diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 94086eefb0..1176d51206 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,41 @@ JUCE breaking changes Develop ======= +Change +----- +Multiple changes to low-level, non-public JNI and Android APIs. + +Possible Issues +--------------- +If you were using any non-public, low-level JNI macros, calling java code or recieving JNI callbacks, then your code will probably no longer work. See the forum for further details. + +Workaround +---------- +See the forum for further details. + +Rationale +--------- +See the forum for further details. + + +Change +----- +The minimum Android version for a JUCE app is now Android 4.1 + +Possible Issues +--------------- +Your app may not run on very old versions of Android (less than 5% of the devices). + +Workaround +---------- +There is no workaround. + +Rationale +--------- +Less than 5% of all devices in the world run versions of Android older than Android +4.1. In the interest of keeping JUCE code clean and lean, we must depricate support +for very old Android versions from time to time. + Version 5.4.0 ============= @@ -94,7 +129,6 @@ The new createAndAddParameter method is much more flexible and enables any parameter types derived from RangedAudioParameter to be managed by the AudioProcessorValueTreeState. - Change ------ The Projucer's per-exporter Android SDK/NDK path options have been removed. @@ -114,7 +148,6 @@ Rationale Having multiple places where the paths could be set was confusing and could cause unexpected mismatches. - Change ------ SystemStats::getDeviceDescription() will now return the device code on iOS e.g. diff --git a/examples/DemoRunner/Builds/Android/app/build.gradle b/examples/DemoRunner/Builds/Android/app/build.gradle index c384afbb24..aef27b2f8d 100644 --- a/examples/DemoRunner/Builds/Android/app/build.gradle +++ b/examples/DemoRunner/Builds/Android/app/build.gradle @@ -83,11 +83,21 @@ android { } } -repositories { -} + sourceSets { + main.java.srcDirs += + ["../../../../../modules/juce_audio_devices/native/java", + "../../../../../modules/juce_core/native/java", + "../../../../../modules/juce_gui_basics/native/java", + "../../../../../modules/juce_gui_extra/native/java", + "../../../../../modules/juce_opengl/native/java", + "../../../../../modules/juce_video/native/java"] + } -dependencies { -} + repositories { + } + + dependencies { + } } diff --git a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml index f302ae11ae..d193df8f09 100644 --- a/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml +++ b/examples/DemoRunner/Builds/Android/app/src/main/AndroidManifest.xml @@ -12,15 +12,15 @@ - - + - diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt index e7c72285be..28489c0538 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt @@ -8,7 +8,7 @@ SET(BINARY_NAME "juce_jni") add_library("cpufeatures" STATIC "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") set_source_files_properties("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion -Wno-gnu-statement-expression") -add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=23" "-DJUCE_ANDROID_ACTIVITY_CLASSNAME=com_juce_audioperformancetest_AudioPerformanceTest" "-DJUCE_ANDROID_ACTIVITY_CLASSPATH=\"com/juce/audioperformancetest/AudioPerformanceTest\"" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME=com_juce_audioperformancetest_SharingContentProvider" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH=\"com/juce/audioperformancetest/SharingContentProvider\"" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCE_ANDROID_GL_ES_VERSION_3_0=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000") +add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=23" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCE_ANDROID_GL_ES_VERSION_3_0=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000") include_directories( AFTER "../../../JuceLibraryCode" @@ -558,6 +558,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_Uuid.h" "../../../../../modules/juce_core/misc/juce_WindowsRegistry.h" "../../../../../modules/juce_core/native/juce_android_Files.cpp" + "../../../../../modules/juce_core/native/juce_android_JNIHelpers.cpp" "../../../../../modules/juce_core/native/juce_android_JNIHelpers.h" "../../../../../modules/juce_core/native/juce_android_Misc.cpp" "../../../../../modules/juce_core/native/juce_android_Network.cpp" @@ -1828,6 +1829,7 @@ set_source_files_properties("../../../../../modules/juce_core/misc/juce_Uuid.cpp set_source_files_properties("../../../../../modules/juce_core/misc/juce_Uuid.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/misc/juce_WindowsRegistry.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Files.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_core/native/juce_android_JNIHelpers.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_JNIHelpers.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Misc.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Network.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/extras/AudioPerformanceTest/Builds/Android/app/build.gradle b/extras/AudioPerformanceTest/Builds/Android/app/build.gradle index a9566718b6..75b3a35d08 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/build.gradle +++ b/extras/AudioPerformanceTest/Builds/Android/app/build.gradle @@ -86,11 +86,19 @@ android { } } -repositories { -} + sourceSets { + main.java.srcDirs += + ["../../../../../modules/juce_audio_devices/native/java", + "../../../../../modules/juce_core/native/java", + "../../../../../modules/juce_gui_basics/native/java", + "../../../../../modules/juce_gui_extra/native/java"] + } -dependencies { -} + repositories { + } + + dependencies { + } } diff --git a/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml b/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml index 447fd462cd..78eb252166 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml +++ b/extras/AudioPerformanceTest/Builds/Android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + package="com.juce.AudioPerformanceTest"> @@ -9,8 +9,8 @@ - - + diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt index 937852bf08..fa631ed1f0 100644 --- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt @@ -8,7 +8,7 @@ SET(BINARY_NAME "juce_jni") add_library("cpufeatures" STATIC "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") set_source_files_properties("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion -Wno-gnu-statement-expression") -add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=23" "-DJUCE_ANDROID_ACTIVITY_CLASSNAME=com_roli_juce_pluginhost_AudioPluginHost" "-DJUCE_ANDROID_ACTIVITY_CLASSPATH=\"com/roli/juce/pluginhost/AudioPluginHost\"" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME=com_roli_juce_pluginhost_SharingContentProvider" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH=\"com/roli/juce/pluginhost/SharingContentProvider\"" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCE_ANDROID_GL_ES_VERSION_3_0=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000") +add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=23" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCE_ANDROID_GL_ES_VERSION_3_0=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000") include_directories( AFTER "../../../../../modules/juce_audio_processors/format_types/VST3_SDK" @@ -575,6 +575,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_Uuid.h" "../../../../../modules/juce_core/misc/juce_WindowsRegistry.h" "../../../../../modules/juce_core/native/juce_android_Files.cpp" + "../../../../../modules/juce_core/native/juce_android_JNIHelpers.cpp" "../../../../../modules/juce_core/native/juce_android_JNIHelpers.h" "../../../../../modules/juce_core/native/juce_android_Misc.cpp" "../../../../../modules/juce_core/native/juce_android_Network.cpp" @@ -1918,6 +1919,7 @@ set_source_files_properties("../../../../../modules/juce_core/misc/juce_Uuid.cpp set_source_files_properties("../../../../../modules/juce_core/misc/juce_Uuid.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/misc/juce_WindowsRegistry.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Files.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_core/native/juce_android_JNIHelpers.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_JNIHelpers.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Misc.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Network.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/extras/AudioPluginHost/Builds/Android/app/build.gradle b/extras/AudioPluginHost/Builds/Android/app/build.gradle index 31fc97b388..a6601bfbd1 100644 --- a/extras/AudioPluginHost/Builds/Android/app/build.gradle +++ b/extras/AudioPluginHost/Builds/Android/app/build.gradle @@ -83,11 +83,21 @@ android { } } -repositories { -} + sourceSets { + main.java.srcDirs += + ["../../../../../modules/juce_audio_devices/native/java", + "../../../../../modules/juce_core/native/java", + "../../../../../modules/juce_gui_basics/native/java", + "../../../../../modules/juce_gui_extra/native/java", + "../../../../../modules/juce_opengl/native/java", + "../../../../../modules/juce_video/native/java"] + } -dependencies { -} + repositories { + } + + dependencies { + } } diff --git a/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml b/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml index 7a68c4f7d9..100f76348b 100644 --- a/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml +++ b/extras/AudioPluginHost/Builds/Android/app/src/main/AndroidManifest.xml @@ -11,8 +11,8 @@ - - + diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt index 5a8da270fe..49765aedcd 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt @@ -8,7 +8,7 @@ SET(BINARY_NAME "juce_jni") add_library("cpufeatures" STATIC "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c") set_source_files_properties("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c" PROPERTIES COMPILE_FLAGS "-Wno-sign-conversion -Wno-gnu-statement-expression") -add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=10" "-DJUCE_ANDROID_ACTIVITY_CLASSNAME=com_juce_networkgraphicsdemo_JUCENetworkGraphicsDemo" "-DJUCE_ANDROID_ACTIVITY_CLASSPATH=\"com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo\"" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME=com_juce_networkgraphicsdemo_SharingContentProvider" "-DJUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH=\"com/juce/networkgraphicsdemo/SharingContentProvider\"" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000") +add_definitions("-DJUCE_ANDROID=1" "-DJUCE_ANDROID_API_VERSION=10" "-DJUCE_PUSH_NOTIFICATIONS=1" "-DJUCER_ANDROIDSTUDIO_7F0E4A25=1" "-DJUCE_APP_VERSION=1.0.0" "-DJUCE_APP_VERSION_HEX=0x10000") include_directories( AFTER "../../../JuceLibraryCode" @@ -562,6 +562,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_Uuid.h" "../../../../../modules/juce_core/misc/juce_WindowsRegistry.h" "../../../../../modules/juce_core/native/juce_android_Files.cpp" + "../../../../../modules/juce_core/native/juce_android_JNIHelpers.cpp" "../../../../../modules/juce_core/native/juce_android_JNIHelpers.h" "../../../../../modules/juce_core/native/juce_android_Misc.cpp" "../../../../../modules/juce_core/native/juce_android_Network.cpp" @@ -1907,6 +1908,7 @@ set_source_files_properties("../../../../../modules/juce_core/misc/juce_Uuid.cpp set_source_files_properties("../../../../../modules/juce_core/misc/juce_Uuid.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/misc/juce_WindowsRegistry.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Files.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties("../../../../../modules/juce_core/native/juce_android_JNIHelpers.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_JNIHelpers.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Misc.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties("../../../../../modules/juce_core/native/juce_android_Network.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/build.gradle b/extras/NetworkGraphicsDemo/Builds/Android/app/build.gradle index 98a8bc1afc..ea7b9aa70e 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/build.gradle +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/build.gradle @@ -83,11 +83,20 @@ android { } } -repositories { -} + sourceSets { + main.java.srcDirs += + ["../../../../../modules/juce_audio_devices/native/java", + "../../../../../modules/juce_core/native/java", + "../../../../../modules/juce_gui_basics/native/java", + "../../../../../modules/juce_gui_extra/native/java", + "../../../../../modules/juce_opengl/native/java"] + } -dependencies { -} + repositories { + } + + dependencies { + } } diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml b/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml index 4c8c56ab3d..00841a34bc 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + package="com.juce.NetworkGraphicsDemo"> @@ -10,8 +10,8 @@ - - + diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h index b81bd63b8d..924a5e4fd5 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h @@ -55,6 +55,7 @@ public: { case ProjectType::Target::GUIApp: case ProjectType::Target::StaticLibrary: + case ProjectType::Target::DynamicLibrary: case ProjectType::Target::StandalonePlugIn: return true; default: @@ -93,8 +94,8 @@ public: } //============================================================================== - ValueWithDefault androidJavaLibs, androidRepositories, androidDependencies, androidScreenOrientation, androidActivityClass, - androidActivitySubClassName, androidActivityBaseClassName, androidManifestCustomXmlElements, androidVersionCode, + ValueWithDefault androidJavaLibs, androidAdditionalJavaFolders, androidAdditionalResourceFolders, androidRepositories, androidDependencies, androidScreenOrientation, + androidCustomActivityClass, androidCustomApplicationClass, androidManifestCustomXmlElements, androidVersionCode, androidMinimumSDK, androidTheme, androidSharedLibraries, androidStaticLibraries, androidExtraAssetsFolder, androidOboeRepositoryPath, androidInternetNeeded, androidMicNeeded, androidCameraNeeded, androidBluetoothNeeded, androidExternalReadPermission, androidExternalWritePermission, androidInAppBillingPermission, androidVibratePermission,androidOtherPermissions, @@ -105,15 +106,16 @@ public: AndroidProjectExporter (Project& p, const ValueTree& t) : ProjectExporter (p, t), androidJavaLibs (settings, Ids::androidJavaLibs, getUndoManager()), + androidAdditionalJavaFolders (settings, Ids::androidAdditionalJavaFolders, getUndoManager()), + androidAdditionalResourceFolders (settings, Ids::androidAdditionalResourceFolders, getUndoManager()), androidRepositories (settings, Ids::androidRepositories, getUndoManager()), androidDependencies (settings, Ids::androidDependencies, getUndoManager()), androidScreenOrientation (settings, Ids::androidScreenOrientation, getUndoManager(), "unspecified"), - androidActivityClass (settings, Ids::androidActivityClass, getUndoManager(), createDefaultClassName()), - androidActivitySubClassName (settings, Ids::androidActivitySubClassName, getUndoManager()), - androidActivityBaseClassName (settings, Ids::androidActivityBaseClassName, getUndoManager(), "Activity"), + androidCustomActivityClass (settings, Ids::androidCustomActivityClass, getUndoManager()), + androidCustomApplicationClass (settings, Ids::androidCustomApplicationClass, getUndoManager(), "com.roli.juce.JuceApp"), androidManifestCustomXmlElements (settings, Ids::androidManifestCustomXmlElements, getUndoManager()), androidVersionCode (settings, Ids::androidVersionCode, getUndoManager(), "1"), - androidMinimumSDK (settings, Ids::androidMinimumSDK, getUndoManager(), "10"), + androidMinimumSDK (settings, Ids::androidMinimumSDK, getUndoManager(), "16"), androidTheme (settings, Ids::androidTheme, getUndoManager()), androidSharedLibraries (settings, Ids::androidSharedLibraries, getUndoManager()), androidStaticLibraries (settings, Ids::androidStaticLibraries, getUndoManager()), @@ -201,15 +203,11 @@ public: auto appFolder = targetFolder.getChildFile (isLibrary() ? "lib" : "app"); removeOldFiles (targetFolder); - - if (! isLibrary()) - copyJavaFiles (modules); - copyExtraResourceFiles(); writeFile (targetFolder, "settings.gradle", isLibrary() ? "include ':lib'" : "include ':app'"); writeFile (targetFolder, "build.gradle", getProjectBuildGradleFileContent()); - writeFile (appFolder, "build.gradle", getAppBuildGradleFileContent()); + writeFile (appFolder, "build.gradle", getAppBuildGradleFileContent (modules)); writeFile (targetFolder, "local.properties", getLocalPropertiesFileContent()); writeFile (targetFolder, "gradle/wrapper/gradle-wrapper.properties", getGradleWrapperPropertiesFileContent()); @@ -583,7 +581,7 @@ private: } //============================================================================== - String getAppBuildGradleFileContent() const + String getAppBuildGradleFileContent (const OwnedArray& modules) const { MemoryOutputStream mo; mo << "apply plugin: 'com.android." << (isLibrary() ? "library" : "application") << "'" << newLine << newLine; @@ -603,6 +601,7 @@ private: mo << getAndroidProductFlavours() << newLine; mo << getAndroidVariantFilter() << newLine; + mo << getAndroidJavaSourceSets (modules) << newLine; mo << getAndroidRepositories() << newLine; mo << getAndroidDependencies() << newLine; mo << getApplyPlugins() << newLine; @@ -769,12 +768,12 @@ private: auto repositories = StringArray::fromLines (androidRepositories.get().toString()); - mo << "repositories {" << newLine; + mo << " repositories {" << newLine; for (auto& r : repositories) - mo << " " << r << newLine; + mo << " " << r << newLine; - mo << "}" << newLine; + mo << " }" << newLine; return mo.toString(); } @@ -782,21 +781,21 @@ private: String getAndroidDependencies() const { MemoryOutputStream mo; - mo << "dependencies {" << newLine; + mo << " dependencies {" << newLine; for (auto& d : StringArray::fromLines (androidDependencies.get().toString())) - mo << " " << d << newLine; + mo << " " << d << newLine; for (auto& d : StringArray::fromLines (androidJavaLibs.get().toString())) - mo << " implementation files('libs/" << File (d).getFileName() << "')" << newLine; + mo << " implementation files('libs/" << File (d).getFileName() << "')" << newLine; if (androidEnableRemoteNotifications.get()) { - mo << " 'com.google.firebase:firebase-core:11.4.0'" << newLine; - mo << " compile 'com.google.firebase:firebase-messaging:11.4.0'" << newLine; + mo << " 'com.google.firebase:firebase-core:11.4.0'" << newLine; + mo << " compile 'com.google.firebase:firebase-messaging:11.4.0'" << newLine; } - mo << "}" << newLine; + mo << " }" << newLine; return mo.toString(); } @@ -811,6 +810,90 @@ private: return mo.toString(); } + void addModuleJavaFolderToSourceSet(StringArray& javaSourceSets, const File& javacore) const + { + if (javacore.isDirectory()) + { + auto appFolder = getTargetFolder().getChildFile ("app"); + + RelativePath relativePath (javacore, appFolder, RelativePath::buildTargetFolder); + javaSourceSets.add (relativePath.toUnixStyle()); + } + } + + String getAndroidJavaSourceSets (const OwnedArray& modules) const + { + auto javaSourceSets = getSourceSetArrayFor (androidAdditionalJavaFolders.get().toString()); + auto resourceSets = getSourceSetArrayFor (androidAdditionalResourceFolders.get().toString()); + + for (auto* module : modules) + { + auto javaFolder = module->getFolder().getChildFile ("native").getChildFile ("javacore"); + + addModuleJavaFolderToSourceSet (javaSourceSets, javaFolder.getChildFile("init")); + + if (! isLibrary()) + addModuleJavaFolderToSourceSet (javaSourceSets, javaFolder.getChildFile("app")); + } + + MemoryOutputStream mo; + mo << " sourceSets {" << newLine; + mo << getSourceSetStringFor ("main.java.srcDirs", javaSourceSets); + mo << newLine; + mo << getSourceSetStringFor ("main.res.srcDirs", resourceSets); + mo << " }" << newLine; + + return mo.toString(); + } + + StringArray getSourceSetArrayFor (const String& srcDirs) const + { + StringArray sourceSets; + + for (auto folder : StringArray::fromLines (srcDirs)) + { + if (File::isAbsolutePath (folder)) + { + sourceSets.add (folder); + } + else + { + auto appFolder = getTargetFolder().getChildFile ("app"); + + auto relativePath = RelativePath (folder, RelativePath::projectFolder) + .rebased (getProject().getProjectFolder(), appFolder, + RelativePath::buildTargetFolder); + + sourceSets.add (relativePath.toUnixStyle()); + } + } + + return sourceSets; + } + + static String getSourceSetStringFor (const String& type, const StringArray& srcDirs) + { + String s; + + s << " " << type << " +=" << newLine; + s << " ["; + + bool isFirst = true; + + for (auto sourceSet : srcDirs) + { + if (! isFirst) + s << "," << newLine << " "; + + isFirst = false; + s << "\"" << sourceSet << "\""; + } + + s << "]" << newLine; + + return s; + } + //============================================================================== String getLocalPropertiesFileContent() const { @@ -835,7 +918,17 @@ private: //============================================================================== void createBaseExporterProperties (PropertyListBuilder& props) { - props.add (new TextPropertyComponent (androidJavaLibs, "Java Libraries to Include", 32768, true), + props.add (new TextPropertyComponent (androidAdditionalJavaFolders, "Java Source code folders", 32768, true), + "Folders inside which additional java source files can be found (one per line). For example, if you " + "are using your own Activity you should place the java files for this into a folder and add the folder " + "path to this field."); + + props.add (new TextPropertyComponent (androidAdditionalResourceFolders, "Resource folders", 32768, true), + "Folders inside which additional resource files can be found (one per line). For example, if you " + "want to add your own layout xml files then you should place a layout xml file inside a folder and add " + "the folder path to this field."); + + props.add (new TextPropertyComponent (androidJavaLibs, "Java libraries to include", 32768, true), "Java libs (JAR files) (one per line). These will be copied to app/libs folder and \"implementation files\" " "dependency will be automatically added to module \"dependencies\" section for each library, so do " "not add the dependency yourself."); @@ -853,24 +946,20 @@ private: { "unspecified", "portrait", "landscape" }), "The screen orientations that this app should support"); - props.add (new TextPropertyComponent (androidActivityClass, "Android Activity Class Name", 256, false), - "The full java class name to use for the app's Activity class."); + props.add (new TextPropertyComponent (androidCustomActivityClass, "Custom Android Activity", 256, false), + "If not empty, specifies the Android Activity class name stored in the app's manifest which " + "should be used instead of Android's default Activity."); - props.add (new TextPropertyComponent (androidActivitySubClassName, "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 TextPropertyComponent (androidActivityBaseClassName, "Android Activity Base Class", 256, false), - "If not empty, specifies the base class to use for your activity. If custom base class is " - "specified, that base class should be a sub-class of android.app.Activity. When empty, Activity " - "(android.app.Activity) will be used as the base class. " - "Use this if you would like to use your own Android Activity base class."); + props.add (new TextPropertyComponent (androidCustomApplicationClass, "Custom Android Application", 256, false), + "If not empty, specifies the Android Application class name stored in the app's manifest which " + "should be used instead of JUCE's default JuceApp class. If you specify a custom App then you must " + "call com.roli.juce.Java.initialiseJUCE somewhere in your code before calling any JUCE functions."); props.add (new TextPropertyComponent (androidVersionCode, "Android Version Code", 32, false), "An integer value that represents the version of the application code, relative to other versions."); props.add (new TextPropertyComponent (androidMinimumSDK, "Minimum SDK Version", 32, false), - "The number of the minimum version of the Android SDK that the app requires"); + "The number of the minimum version of the Android SDK that the app requires (must be 16 or higher)."); props.add (new TextPropertyComponent (androidExtraAssetsFolder, "Extra Android Assets", 256, false), "A path to a folder (relative to the project folder) which contains extra android assets."); @@ -950,286 +1039,6 @@ private: } //============================================================================== - String createDefaultClassName() const - { - auto s = project.getBundleIdentifierString().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.getProjectFilenameRootString(), false, true, false); - } - - //============================================================================== - void copyJavaFiles (const OwnedArray& modules) const - { - if (auto* coreModule = getCoreModule (modules)) - { - auto package = getActivityClassPackage(); - auto targetFolder = getTargetFolder(); - - auto inAppBillingPath = String ("com.android.vending.billing").replaceCharacter ('.', File::getSeparatorChar()); - auto javaSourceFolder = coreModule->getFolder().getChildFile ("native").getChildFile ("java"); - auto javaInAppBillingTarget = targetFolder.getChildFile ("app/src/main/java").getChildFile (inAppBillingPath); - auto javaTarget = targetFolder.getChildFile ("app/src/main/java") - .getChildFile (package.replaceCharacter ('.', File::getSeparatorChar())); - auto libTarget = targetFolder.getChildFile ("app/libs"); - libTarget.createDirectory(); - - copyActivityJavaFiles (javaSourceFolder, javaTarget, package); - copyServicesJavaFiles (javaSourceFolder, javaTarget, package); - copyProviderJavaFile (javaSourceFolder, javaTarget, package); - copyAdditionalJavaFiles (javaSourceFolder, javaInAppBillingTarget); - copyAdditionalJavaLibs (libTarget); - } - } - - void copyActivityJavaFiles (const File& javaSourceFolder, const File& targetFolder, const String& package) const - { - if (androidActivityClass.get().toString().contains ("_")) - throw SaveError ("Your Android activity class name or path may not contain any underscores! Try a project name without underscores."); - - auto className = getActivityName(); - - if (className.isEmpty()) - throw SaveError ("Invalid Android Activity class name: " + androidActivityClass.get().toString()); - - createDirectoryOrThrow (targetFolder); - - auto activityCode = getActivityCode (javaSourceFolder, className, package); - - auto javaDestFile = targetFolder.getChildFile (className + ".java"); - overwriteFileIfDifferentOrThrow (javaDestFile, activityCode); - } - - String getActivityCode (const File& javaSourceFolder, const String& className, const String& package) const - { - auto runtimePermissionsCode = getRuntimePermissionsCode (javaSourceFolder, className); - auto midiCode = getMidiCode (javaSourceFolder, className); - auto webViewCode = getWebViewCode (javaSourceFolder); - auto cameraCode = getCameraCode (javaSourceFolder); - auto videoCode = getVideoCode (javaSourceFolder); - - auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java"); - auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString()); - - { - MemoryOutputStream newFile; - - for (auto& line : javaSourceLines) - { - if (line.contains ("$$JuceAndroidMidiImports$$")) - newFile << midiCode.imports; - else if (line.contains ("$$JuceAndroidMidiCode$$")) - newFile << midiCode.main; - else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$")) - newFile << runtimePermissionsCode; - else if (line.contains ("$$JuceAndroidWebViewImports$$")) - newFile << webViewCode.imports; - else if (line.contains ("$$JuceAndroidWebViewNativeCode$$")) - newFile << webViewCode.native; - else if (line.contains ("$$JuceAndroidWebViewCode$$")) - newFile << webViewCode.main; - else if (line.contains ("$$JuceAndroidCameraImports$$")) - newFile << cameraCode.imports; - else if (line.contains ("$$JuceAndroidCameraCode$$")) - newFile << cameraCode.main; - else if (line.contains ("$$JuceAndroidVideoImports$$")) - newFile << videoCode.imports; - else if (line.contains ("$$JuceAndroidVideoCode$$")) - newFile << videoCode.main; - else - newFile << line.replace ("$$JuceAppActivityBaseClass$$", androidActivityBaseClassName.get().toString()) - .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); - - return javaSourceLines.joinIntoString (newLine); - } - - String getRuntimePermissionsCode (const File& javaSourceFolder, const String& className) const - { - if (static_cast (androidMinimumSDK.get()) >= 23) - { - auto javaRuntimePermissions = javaSourceFolder.getChildFile ("AndroidRuntimePermissions.java"); - return javaRuntimePermissions.loadFileAsString().replace ("JuceAppActivity", className); - } - - return {}; - } - - struct MidiCode - { - String imports; - String main; - }; - - MidiCode getMidiCode (const File& javaSourceFolder, const String& className) const - { - String juceMidiCode, juceMidiImports; - - juceMidiImports << newLine; - - if (static_cast (androidMinimumSDK.get()) >= 23) - { - auto javaAndroidMidi = javaSourceFolder.getChildFile ("AndroidMidi.java"); - - juceMidiImports << "import android.media.midi.*;" << newLine - << "import android.bluetooth.*;" << newLine - << "import android.bluetooth.le.*;" << newLine; - - juceMidiCode = javaAndroidMidi.loadFileAsString().replace ("JuceAppActivity", className); - } - else - { - juceMidiCode = javaSourceFolder.getChildFile ("AndroidMidiFallback.java") - .loadFileAsString() - .replace ("JuceAppActivity", className); - } - - return { juceMidiImports, juceMidiCode }; - } - - struct WebViewCode - { - String imports; - String native; - String main; - }; - - WebViewCode getWebViewCode (const File& javaSourceFolder) const - { - String juceWebViewImports, juceWebViewCodeNative, juceWebViewCode; - - if (static_cast (androidMinimumSDK.get()) >= 23) - juceWebViewImports << "import android.webkit.WebResourceError;" << newLine; - - if (static_cast (androidMinimumSDK.get()) >= 21) - juceWebViewImports << "import android.webkit.WebResourceRequest;" << newLine; - - if (static_cast (androidMinimumSDK.get()) >= 11) - juceWebViewImports << "import android.webkit.WebResourceResponse;" << newLine; - - auto javaWebViewFile = javaSourceFolder.getChildFile ("AndroidWebView.java"); - auto juceWebViewCodeAll = javaWebViewFile.loadFileAsString(); - - if (static_cast (androidMinimumSDK.get()) <= 10) - { - juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi1_10", false, false) - .upToFirstOccurrenceOf ("WebViewApi1_10$$", false, false); - } - else - { - if (static_cast (androidMinimumSDK.get()) >= 23) - { - juceWebViewCodeNative << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewNativeApi23", false, false) - .upToFirstOccurrenceOf ("WebViewNativeApi23$$", false, false); - - juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi23", false, false) - .upToFirstOccurrenceOf ("WebViewApi23$$", false, false); - } - - if (static_cast (androidMinimumSDK.get()) >= 21) - { - juceWebViewCodeNative << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewNativeApi21", false, false) - .upToFirstOccurrenceOf ("WebViewNativeApi21$$", false, false); - - juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi21", false, false) - .upToFirstOccurrenceOf ("WebViewApi21$$", false, false); - } - else - { - juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi11_20", false, false) - .upToFirstOccurrenceOf ("WebViewApi11_20$$", false, false); - } - } - - return { juceWebViewImports, juceWebViewCodeNative, juceWebViewCode }; - } - - struct CameraCode - { - String imports; - String main; - }; - - CameraCode getCameraCode (const File& javaSourceFolder) const - { - String juceCameraImports, juceCameraCode; - - if (static_cast (androidMinimumSDK.get()) >= 21) - { - juceCameraImports << "import android.hardware.camera2.*;" << newLine; - - auto javaCameraFile = javaSourceFolder.getChildFile ("AndroidCamera.java"); - auto juceCameraCodeAll = javaCameraFile.loadFileAsString(); - - juceCameraCode << juceCameraCodeAll.fromFirstOccurrenceOf ("$$CameraApi21", false, false) - .upToFirstOccurrenceOf ("CameraApi21$$", false, false); - } - - return { juceCameraImports, juceCameraCode }; - } - - struct VideoCode - { - String imports; - String main; - }; - - VideoCode getVideoCode (const File& javaSourceFolder) const - { - String juceVideoImports, juceVideoCode; - - if (static_cast (androidMinimumSDK.get()) >= 21) - { - juceVideoImports << "import android.database.ContentObserver;" << newLine; - juceVideoImports << "import android.media.session.*;" << newLine; - juceVideoImports << "import android.media.MediaMetadata;" << newLine; - - auto javaVideoFile = javaSourceFolder.getChildFile ("AndroidVideo.java"); - auto juceVideoCodeAll = javaVideoFile.loadFileAsString(); - - juceVideoCode << juceVideoCodeAll.fromFirstOccurrenceOf ("$$VideoApi21", false, false) - .upToFirstOccurrenceOf ("VideoApi21$$", false, false); - } - - return { juceVideoImports, juceVideoCode }; - } - - void copyAdditionalJavaFiles (const File& sourceFolder, const File& targetFolder) const - { - auto inAppBillingJavaFileName = String ("IInAppBillingService.java"); - - auto inAppBillingJavaSrcFile = sourceFolder.getChildFile (inAppBillingJavaFileName); - auto inAppBillingJavaDestFile = targetFolder.getChildFile (inAppBillingJavaFileName); - - createDirectoryOrThrow (targetFolder); - - jassert (inAppBillingJavaSrcFile.existsAsFile()); - - if (inAppBillingJavaSrcFile.existsAsFile()) - inAppBillingJavaSrcFile.copyFileTo (inAppBillingJavaDestFile); - } - void copyAdditionalJavaLibs (const File& targetFolder) const { auto libPaths = StringArray::fromLines (androidJavaLibs.get().toString()); @@ -1245,58 +1054,6 @@ private: } } - void copyServicesJavaFiles (const File& javaSourceFolder, const File& targetFolder, const String& package) const - { - if (androidEnableRemoteNotifications.get()) - { - String instanceIdFileName ("JuceFirebaseInstanceIdService.java"); - String messagingFileName ("JuceFirebaseMessagingService.java"); - - File instanceIdFile (javaSourceFolder.getChildFile (instanceIdFileName)); - File messagingFile (javaSourceFolder.getChildFile (messagingFileName)); - - jassert (instanceIdFile.existsAsFile()); - jassert (messagingFile .existsAsFile()); - - Array files; - files.add (instanceIdFile); - files.add (messagingFile); - - for (auto& file : files) - { - auto newContent = file.loadFileAsString() - .replace ("package com.juce;", "package " + package + ";"); - auto targetFile = targetFolder.getChildFile (file.getFileName()); - overwriteFileIfDifferentOrThrow (targetFile, newContent); - } - } - } - - void copyProviderJavaFile (const File& javaSourceFolder, const File& targetFolder, const String& package) const - { - auto providerFile = javaSourceFolder.getChildFile ("AndroidSharingContentProvider.java"); - - jassert (providerFile.existsAsFile()); - - auto targetFile = targetFolder.getChildFile ("SharingContentProvider.java"); - - auto fileContent = providerFile.loadFileAsString() - .replace ("package com.juce;", "package " + package + ";"); - - auto commonStart = fileContent.upToFirstOccurrenceOf ("$$ContentProviderApi11", false, false); - auto commonEnd = fileContent.fromFirstOccurrenceOf ("ContentProviderApi11$$", false, false); - - auto middleContent = static_cast (androidMinimumSDK.get()) >= 11 - ? fileContent.fromFirstOccurrenceOf ("$$ContentProviderApi11", false, false) - .upToFirstOccurrenceOf ("ContentProviderApi11$$", false, false) - : String(); - - auto newContent = commonStart; - newContent << middleContent << commonEnd; - - overwriteFileIfDifferentOrThrow (targetFile, newContent); - } - void copyExtraResourceFiles() const { for (ConstConfigIterator config (*this); config.next();) @@ -1346,26 +1103,23 @@ private: } } - String getActivityName() const + String getActivityClass() const { - return androidActivityClass.get().toString().fromLastOccurrenceOf (".", false, false); - } - - String getActivitySubClassName() const - { - auto activityPath = androidActivitySubClassName.get().toString(); + auto customActivityClass = androidCustomActivityClass.get().toString(); - return (activityPath.isEmpty()) ? getActivityName() : activityPath.fromLastOccurrenceOf (".", false, false); + return (customActivityClass.isEmpty()) ? "android.app.Activity" : customActivityClass; } - String getActivityClassPackage() const + String getApplicationClass() const { - return androidActivityClass.get().toString().upToLastOccurrenceOf (".", false, false); + auto customApplicationClass = androidCustomApplicationClass.get().toString(); + + return (customApplicationClass.isEmpty()) ? "com.roli.juce.JuceApp" : customApplicationClass; } String getJNIActivityClassName() const { - return androidActivityClass.get().toString().replaceCharacter ('.', '/'); + return getActivityClass().replaceCharacter ('.', '/'); } static LibraryModule* getCoreModule (const OwnedArray& modules) @@ -1572,34 +1326,17 @@ private: 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() + "\""); - defines.set ("JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME", getSharingContentProviderClassName().replaceCharacter('.', '_')); - defines.set ("JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH", "\"" + getSharingContentProviderClassName().replaceCharacter('.', '/') + "\""); defines.set ("JUCE_PUSH_NOTIFICATIONS", "1"); if (androidInAppBillingPermission.get()) defines.set ("JUCE_IN_APP_PURCHASES", "1"); - if (androidEnableRemoteNotifications.get()) - { - auto instanceIdClassName = getActivityClassPackage() + ".JuceFirebaseInstanceIdService"; - auto messagingClassName = getActivityClassPackage() + ".JuceFirebaseMessagingService"; - defines.set ("JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME", instanceIdClassName.replaceCharacter ('.', '_')); - defines.set ("JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME", messagingClassName.replaceCharacter ('.', '_')); - } - if (supportsGLv3()) defines.set ("JUCE_ANDROID_GL_ES_VERSION_3_0", "1"); return defines; } - String getSharingContentProviderClassName() const - { - return getActivityClassPackage() + ".SharingContentProvider"; - } - StringPairArray getProjectPreprocessorDefs() const { auto defines = getAndroidPreprocessorDefs(); @@ -1737,7 +1474,7 @@ private: setAttributeIfNotPresent (*manifest, "xmlns:android", "http://schemas.android.com/apk/res/android"); setAttributeIfNotPresent (*manifest, "android:versionCode", androidVersionCode.get()); setAttributeIfNotPresent (*manifest, "android:versionName", project.getVersionString()); - setAttributeIfNotPresent (*manifest, "package", getActivityClassPackage()); + setAttributeIfNotPresent (*manifest, "package", project.getBundleIdentifierString()); return manifest; } @@ -1797,6 +1534,7 @@ private: { auto* app = getOrCreateChildWithName (manifest, "application"); setAttributeIfNotPresent (*app, "android:label", "@string/app_name"); + setAttributeIfNotPresent (*app, "android:name", getApplicationClass()); if (androidTheme.get().toString().isNotEmpty()) setAttributeIfNotPresent (*app, "android:theme", androidTheme.get()); @@ -1826,7 +1564,7 @@ private: { auto* act = getOrCreateChildWithName (application, "activity"); - setAttributeIfNotPresent (*act, "android:name", getActivitySubClassName()); + setAttributeIfNotPresent (*act, "android:name", getActivityClass()); setAttributeIfNotPresent (*act, "android:label", "@string/app_name"); if (! act->hasAttribute ("android:configChanges")) @@ -1918,8 +1656,9 @@ private: if (androidEnableContentSharing.get()) { auto* provider = application.createNewChildElement ("provider"); - provider->setAttribute ("android:name", getSharingContentProviderClassName()); - provider->setAttribute ("android:authorities", getSharingContentProviderClassName().toLowerCase()); + + provider->setAttribute ("android:name", "com.roli.juce.JuceSharingContentProvider"); + provider->setAttribute ("android:authorities", project.getBundleIdentifierString().toLowerCase() + ".sharingcontentprovider"); provider->setAttribute ("android:grantUriPermissions", "true"); provider->setAttribute ("android:exported", "false"); } diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h index d1a14b1845..deeae76893 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h +++ b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h @@ -189,15 +189,18 @@ namespace Ids DECLARE_ID (cameraPermissionNeeded); DECLARE_ID (cameraPermissionText); DECLARE_ID (androidJavaLibs); + DECLARE_ID (androidAdditionalJavaFolders); + DECLARE_ID (androidAdditionalResourceFolders); DECLARE_ID (androidRepositories); DECLARE_ID (androidDependencies); DECLARE_ID (androidBuildConfigRemoteNotifsConfigFile); DECLARE_ID (androidAdditionalXmlValueResources); DECLARE_ID (androidAdditionalDrawableResources); DECLARE_ID (androidAdditionalRawValueResources); - DECLARE_ID (androidActivityClass); - DECLARE_ID (androidActivitySubClassName); - DECLARE_ID (androidActivityBaseClassName); +// DECLARE_ID (androidActivityClass); // DEPRECATED! + const Identifier androidCustomActivityClass ("androidActivitySubClassName"); // old name is very confusing, but we need to remain backward compatible +// DECLARE_ID (androidActivityBaseClassName); // DEPRECATED! + DECLARE_ID (androidCustomApplicationClass); DECLARE_ID (androidVersionCode); DECLARE_ID (androidSDKPath); DECLARE_ID (androidNDKPath); diff --git a/modules/juce_core/native/java/AndroidMidi.java b/modules/juce_audio_devices/native/java/com/roli/juce/JuceMidiSupport.java similarity index 65% rename from modules/juce_core/native/java/AndroidMidi.java rename to modules/juce_audio_devices/native/java/com/roli/juce/JuceMidiSupport.java index 9e337be8f8..5aba09c8ca 100644 --- a/modules/juce_core/native/java/AndroidMidi.java +++ b/modules/juce_audio_devices/native/java/com/roli/juce/JuceMidiSupport.java @@ -1,999 +1,1075 @@ - //============================================================================== - public class BluetoothManager extends ScanCallback - { - BluetoothManager() - { - } - - public String[] getMidiBluetoothAddresses() - { - return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]); - } - - public String getHumanReadableStringForBluetoothAddress (String address) - { - BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address); - return btDevice.getName(); - } - - public int getBluetoothDeviceStatus (String address) - { - return getAndroidMidiDeviceManager().getBluetoothDeviceStatus (address); - } - - public void startStopScan (boolean shouldStart) - { - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (bluetoothAdapter == null) - { - Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter"); - return; - } - - BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); - - if (bluetoothLeScanner == null) - { - Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner"); - return; - } - - if (shouldStart) - { - ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder(); - scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID)); - - ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder(); - scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES) - .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER) - .setScanMode (ScanSettings.MATCH_MODE_STICKY); - - bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()), - scanSettingsBuilder.build(), - this); - } - else - { - bluetoothLeScanner.stopScan (this); - } - } - - public boolean pairBluetoothMidiDevice(String address) - { - BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address); - - if (btDevice == null) - { - Log.d ("JUCE", "failed to create buletooth device from address"); - return false; - } - - return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice); - } - - public void unpairBluetoothMidiDevice (String address) - { - getAndroidMidiDeviceManager().unpairBluetoothDevice (address); - } - - public void onScanFailed (int errorCode) - { - } - - public void onScanResult (int callbackType, ScanResult result) - { - if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES - || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH) - { - BluetoothDevice device = result.getDevice(); - - if (device != null) - bluetoothMidiDevices.add (device.getAddress()); - } - - if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) - { - Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST"); - BluetoothDevice device = result.getDevice(); - - if (device != null) - { - bluetoothMidiDevices.remove (device.getAddress()); - unpairBluetoothMidiDevice (device.getAddress()); - } - } - } - - public void onBatchScanResults (List results) - { - for (ScanResult result : results) - onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result); - } - - private BluetoothLeScanner scanner; - private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"; - - private HashSet bluetoothMidiDevices = new HashSet(); - } - - public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort - { - private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp); - - public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse) - { - owner = mm; - androidPort = actualPort; - portPath = portPathToUse; - juceHost = hostToUse; - isConnected = false; - } - - @Override - protected void finalize() throws Throwable - { - close(); - super.finalize(); - } - - @Override - public boolean isInputPort() - { - return true; - } - - @Override - public void start() - { - if (owner != null && androidPort != null && ! isConnected) { - androidPort.connect(this); - isConnected = true; - } - } - - @Override - public void stop() - { - if (owner != null && androidPort != null && isConnected) { - androidPort.disconnect(this); - isConnected = false; - } - } - - @Override - public void close() - { - if (androidPort != null) { - try { - androidPort.close(); - } catch (IOException exception) { - Log.d("JUCE", "IO Exception while closing port"); - } - } - - if (owner != null) - owner.removePort (portPath); - - owner = null; - androidPort = null; - } - - @Override - public void onSend (byte[] msg, int offset, int count, long timestamp) - { - if (count > 0) - handleReceive (juceHost, msg, offset, count, timestamp); - } - - @Override - public void onFlush() - {} - - @Override - public void sendMidi (byte[] msg, int offset, int count) - { - } - - MidiDeviceManager owner; - MidiOutputPort androidPort; - MidiPortPath portPath; - long juceHost; - boolean isConnected; - } - - public static class JuceMidiOutputPort implements JuceMidiPort - { - public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse) - { - owner = mm; - androidPort = actualPort; - portPath = portPathToUse; - } - - @Override - protected void finalize() throws Throwable - { - close(); - super.finalize(); - } - - @Override - public boolean isInputPort() - { - return false; - } - - @Override - public void start() - { - } - - @Override - public void stop() - { - } - - @Override - public void sendMidi (byte[] msg, int offset, int count) - { - if (androidPort != null) - { - try { - androidPort.send(msg, offset, count); - } catch (IOException exception) - { - Log.d ("JUCE", "send midi had IO exception"); - } - } - } - - @Override - public void close() - { - if (androidPort != null) { - try { - androidPort.close(); - } catch (IOException exception) { - Log.d("JUCE", "IO Exception while closing port"); - } - } - - if (owner != null) - owner.removePort (portPath); - - owner = null; - androidPort = null; - } - - MidiDeviceManager owner; - MidiInputPort androidPort; - MidiPortPath portPath; - } - - private static class MidiPortPath extends Object - { - public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex) - { - deviceId = deviceIdToUse; - isInput = direction; - portIndex = androidIndex; - - } - - public int deviceId; - public int portIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127)); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortPath other = (MidiPortPath) obj; - return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId); - } - } - - //============================================================================== - public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener - { - //============================================================================== - private class DummyBluetoothGattCallback extends BluetoothGattCallback - { - public DummyBluetoothGattCallback (MidiDeviceManager mm) - { - super(); - owner = mm; - } - - public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) - { - if (newState == BluetoothProfile.STATE_CONNECTED) - { - gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH); - owner.pairBluetoothDeviceStepTwo (gatt.getDevice()); - } - } - public void onServicesDiscovered(BluetoothGatt gatt, int status) {} - public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {} - public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {} - public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {} - public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {} - public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {} - public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {} - public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {} - public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {} - - private MidiDeviceManager owner; - } - - //============================================================================== - private class MidiDeviceOpenTask extends java.util.TimerTask - { - public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse) - { - owner = deviceManager; - midiDevice = device; - btGatt = gattToUse; - } - - @Override - public boolean cancel() - { - synchronized (MidiDeviceOpenTask.class) - { - owner = null; - boolean retval = super.cancel(); - - if (btGatt != null) - { - btGatt.disconnect(); - btGatt.close(); - - btGatt = null; - } - - if (midiDevice != null) - { - try - { - midiDevice.close(); - } - catch (IOException e) - {} - - midiDevice = null; - } - - return retval; - } - } - - public String getBluetoothAddress() - { - synchronized (MidiDeviceOpenTask.class) - { - if (midiDevice != null) - { - MidiDeviceInfo info = midiDevice.getInfo(); - if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH) - { - BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE); - if (btDevice != null) - return btDevice.getAddress(); - } - } - } - - return ""; - } - - public BluetoothGatt getGatt() { return btGatt; } - - public int getID() - { - return midiDevice.getInfo().getId(); - } - - @Override - public void run() - { - synchronized (MidiDeviceOpenTask.class) - { - if (owner != null && midiDevice != null) - owner.onDeviceOpenedDelayed (midiDevice); - } - } - - private MidiDeviceManager owner; - private MidiDevice midiDevice; - private BluetoothGatt btGatt; - } - - //============================================================================== - public MidiDeviceManager() - { - manager = (MidiManager) getSystemService (MIDI_SERVICE); - - if (manager == null) - { - Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service"); - return; - } - - openPorts = new HashMap> (); - midiDevices = new ArrayList>(); - openTasks = new HashMap(); - btDevicesPairing = new HashMap(); - - MidiDeviceInfo[] foundDevices = manager.getDevices(); - for (MidiDeviceInfo info : foundDevices) - onDeviceAdded (info); - - manager.registerDeviceCallback (this, null); - } - - protected void finalize() throws Throwable - { - manager.unregisterDeviceCallback (this); - - synchronized (MidiDeviceManager.class) - { - btDevicesPairing.clear(); - - for (Integer deviceID : openTasks.keySet()) - openTasks.get (deviceID).cancel(); - - openTasks = null; - } - - for (MidiPortPath key : openPorts.keySet()) - openPorts.get (key).get().close(); - - openPorts = null; - - for (Pair device : midiDevices) - { - if (device.second != null) - { - device.second.disconnect(); - device.second.close(); - } - - device.first.close(); - } - - midiDevices.clear(); - - super.finalize(); - } - - public String[] getJuceAndroidMidiInputDevices() - { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); - } - - public String[] getJuceAndroidMidiOutputDevices() - { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); - } - - private String[] getJuceAndroidMidiDevices (int portType) - { - // only update the list when JUCE asks for a new list - synchronized (MidiDeviceManager.class) - { - deviceInfos = getDeviceInfos(); - } - - ArrayList portNames = new ArrayList(); - - int index = 0; - for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index)) - portNames.add (getPortName (portInfo)); - - String[] names = new String[portNames.size()]; - return portNames.toArray (names); - } - - private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput) - { - synchronized (MidiDeviceManager.class) - { - int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT); - MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index); - - if (portInfo != null) - { - // ports must be opened exclusively! - if (openPorts.containsKey (portInfo)) - return null; - - Pair devicePair = getMidiDevicePairForId (portInfo.deviceId); - - if (devicePair != null) - { - MidiDevice device = devicePair.first; - if (device != null) - { - JuceMidiPort juceMidiPort = null; - - if (isInput) - { - MidiOutputPort outputPort = device.openOutputPort(portInfo.portIndex); - - if (outputPort != null) - juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host); - } - else - { - MidiInputPort inputPort = device.openInputPort(portInfo.portIndex); - - if (inputPort != null) - juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo); - } - - if (juceMidiPort != null) - { - openPorts.put(portInfo, new WeakReference(juceMidiPort)); - - return juceMidiPort; - } - } - } - } - } - - return null; - } - - public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) - { - return openMidiPortWithJuceIndex (index, host, true); - } - - public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) - { - return openMidiPortWithJuceIndex (index, 0, false); - } - - /* 0: unpaired, 1: paired, 2: pairing */ - public int getBluetoothDeviceStatus (String address) - { - synchronized (MidiDeviceManager.class) - { - if (! address.isEmpty()) - { - if (findMidiDeviceForBluetoothAddress (address) != null) - return 1; - - if (btDevicesPairing.containsKey (address)) - return 2; - - if (findOpenTaskForBluetoothAddress (address) != null) - return 2; - } - } - - return 0; - } - - public boolean pairBluetoothDevice (BluetoothDevice btDevice) - { - String btAddress = btDevice.getAddress(); - if (btAddress.isEmpty()) - return false; - - synchronized (MidiDeviceManager.class) - { - if (getBluetoothDeviceStatus (btAddress) != 0) - return false; - - - btDevicesPairing.put (btDevice.getAddress(), null); - BluetoothGatt gatt = btDevice.connectGatt (getApplicationContext(), true, new DummyBluetoothGattCallback (this)); - - if (gatt != null) - { - btDevicesPairing.put (btDevice.getAddress(), gatt); - } - else - { - pairBluetoothDeviceStepTwo (btDevice); - } - } - - return true; - } - - public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice) - { - manager.openBluetoothDevice(btDevice, this, null); - } - - public void unpairBluetoothDevice (String address) - { - if (address.isEmpty()) - return; - - synchronized (MidiDeviceManager.class) - { - if (btDevicesPairing.containsKey (address)) - { - BluetoothGatt gatt = btDevicesPairing.get (address); - if (gatt != null) - { - gatt.disconnect(); - gatt.close(); - } - - btDevicesPairing.remove (address); - } - - MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address); - if (openTask != null) - { - int deviceID = openTask.getID(); - openTask.cancel(); - openTasks.remove (deviceID); - } - - Pair midiDevicePair = findMidiDeviceForBluetoothAddress (address); - if (midiDevicePair != null) - { - MidiDevice midiDevice = midiDevicePair.first; - onDeviceRemoved (midiDevice.getInfo()); - - try { - midiDevice.close(); - } - catch (IOException exception) - { - Log.d ("JUCE", "IOException while closing midi device"); - } - } - } - } - - private Pair findMidiDeviceForBluetoothAddress (String address) - { - for (Pair midiDevice : midiDevices) - { - MidiDeviceInfo info = midiDevice.first.getInfo(); - if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH) - { - BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE); - if (btDevice != null && btDevice.getAddress().equals (address)) - return midiDevice; - } - } - - return null; - } - - private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address) - { - for (Integer deviceID : openTasks.keySet()) - { - MidiDeviceOpenTask openTask = openTasks.get (deviceID); - if (openTask.getBluetoothAddress().equals (address)) - return openTask; - } - - return null; - } - - public void removePort (MidiPortPath path) - { - openPorts.remove (path); - } - - public String getInputPortNameForJuceIndex (int index) - { - MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); - if (portInfo != null) - return getPortName (portInfo); - - return ""; - } - - public String getOutputPortNameForJuceIndex (int index) - { - MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); - if (portInfo != null) - return getPortName (portInfo); - - return ""; - } - - public void onDeviceAdded (MidiDeviceInfo info) - { - // only add standard midi devices - if (info.getType() == info.TYPE_BLUETOOTH) - return; - - manager.openDevice (info, this, null); - } - - public void onDeviceRemoved (MidiDeviceInfo info) - { - synchronized (MidiDeviceManager.class) - { - Pair devicePair = getMidiDevicePairForId (info.getId()); - - if (devicePair != null) - { - MidiDevice midiDevice = devicePair.first; - BluetoothGatt gatt = devicePair.second; - - // close all ports that use this device - boolean removedPort = true; - - while (removedPort == true) - { - removedPort = false; - for (MidiPortPath key : openPorts.keySet()) - { - if (key.deviceId == info.getId()) - { - openPorts.get(key).get().close(); - removedPort = true; - break; - } - } - } - - if (gatt != null) - { - gatt.disconnect(); - gatt.close(); - } - - midiDevices.remove (devicePair); - } - } - } - - public void onDeviceStatusChanged (MidiDeviceStatus status) - { - } - - @Override - public void onDeviceOpened (MidiDevice theDevice) - { - synchronized (MidiDeviceManager.class) - { - MidiDeviceInfo info = theDevice.getInfo(); - int deviceID = info.getId(); - BluetoothGatt gatt = null; - boolean isBluetooth = false; - - if (! openTasks.containsKey (deviceID)) - { - if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH) - { - isBluetooth = true; - BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE); - if (btDevice != null) - { - String btAddress = btDevice.getAddress(); - if (btDevicesPairing.containsKey (btAddress)) - { - gatt = btDevicesPairing.get (btAddress); - btDevicesPairing.remove (btAddress); - } - else - { - // unpair was called in the mean time - try - { - Pair midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress()); - if (midiDevicePair != null) - { - gatt = midiDevicePair.second; - - if (gatt != null) - { - gatt.disconnect(); - gatt.close(); - } - } - - theDevice.close(); - } - catch (IOException e) - {} - - return; - } - } - } - - MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt); - openTasks.put (deviceID, openTask); - - new java.util.Timer().schedule (openTask, (isBluetooth ? 2000 : 100)); - } - } - } - - public void onDeviceOpenedDelayed (MidiDevice theDevice) - { - synchronized (MidiDeviceManager.class) - { - int deviceID = theDevice.getInfo().getId(); - - if (openTasks.containsKey (deviceID)) - { - if (! midiDevices.contains(theDevice)) - { - BluetoothGatt gatt = openTasks.get (deviceID).getGatt(); - openTasks.remove (deviceID); - midiDevices.add (new Pair (theDevice, gatt)); - } - } - else - { - // unpair was called in the mean time - MidiDeviceInfo info = theDevice.getInfo(); - BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE); - if (btDevice != null) - { - String btAddress = btDevice.getAddress(); - Pair midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress()); - if (midiDevicePair != null) - { - BluetoothGatt gatt = midiDevicePair.second; - - if (gatt != null) - { - gatt.disconnect(); - gatt.close(); - } - } - } - - try - { - theDevice.close(); - } - catch (IOException e) - {} - } - } - } - - public String getPortName(MidiPortPath path) - { - int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT); - - synchronized (MidiDeviceManager.class) - { - for (MidiDeviceInfo info : deviceInfos) - { - int localIndex = 0; - if (info.getId() == path.deviceId) - { - for (MidiDeviceInfo.PortInfo portInfo : info.getPorts()) - { - int portType = portInfo.getType(); - if (portType == portTypeToFind) - { - int portIndex = portInfo.getPortNumber(); - if (portIndex == path.portIndex) - { - String portName = portInfo.getName(); - if (portName.isEmpty()) - portName = (String) info.getProperties().get(info.PROPERTY_NAME); - - return portName; - } - } - } - } - } - } - - return ""; - } - - public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex) - { - int portIdx = 0; - for (MidiDeviceInfo info : deviceInfos) - { - for (MidiDeviceInfo.PortInfo portInfo : info.getPorts()) - { - if (portInfo.getType() == portType) - { - if (portIdx == juceIndex) - return new MidiPortPath (info.getId(), - (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT), - portInfo.getPortNumber()); - - portIdx++; - } - } - } - - return null; - } - - private MidiDeviceInfo[] getDeviceInfos() - { - synchronized (MidiDeviceManager.class) - { - MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()]; - - int idx = 0; - for (Pair midiDevice : midiDevices) - infos[idx++] = midiDevice.first.getInfo(); - - return infos; - } - } - - private Pair getMidiDevicePairForId (int deviceId) - { - synchronized (MidiDeviceManager.class) - { - for (Pair midiDevice : midiDevices) - if (midiDevice.first.getInfo().getId() == deviceId) - return midiDevice; - } - - return null; - } - - private MidiManager manager; - private HashMap btDevicesPairing; - private HashMap openTasks; - private ArrayList> midiDevices; - private MidiDeviceInfo[] deviceInfos; - private HashMap> openPorts; - } - - public MidiDeviceManager getAndroidMidiDeviceManager() - { - if (getSystemService (MIDI_SERVICE) == null) - return null; - - synchronized (JuceAppActivity.class) - { - if (midiDeviceManager == null) - midiDeviceManager = new MidiDeviceManager(); - } - - return midiDeviceManager; - } - - public BluetoothManager getAndroidBluetoothManager() - { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - if (adapter == null) - return null; - - if (adapter.getBluetoothLeScanner() == null) - return null; - - synchronized (JuceAppActivity.class) - { - if (bluetoothManager == null) - bluetoothManager = new BluetoothManager(); - } - - return bluetoothManager; - } +package com.roli.juce; + + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.Context; +import android.media.midi.MidiDevice; +import android.media.midi.MidiDeviceInfo; +import android.media.midi.MidiDeviceStatus; +import android.media.midi.MidiInputPort; +import android.media.midi.MidiManager; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.BluetoothDevice; +import android.media.midi.MidiOutputPort; +import android.media.midi.MidiReceiver; +import android.os.ParcelUuid; +import android.util.Log; +import android.util.Pair; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import static android.content.Context.MIDI_SERVICE; + +public class JuceMidiSupport +{ + //============================================================================== + public interface JuceMidiPort + { + boolean isInputPort (); + + // start, stop does nothing on an output port + void start (); + + void stop (); + + void close (); + + // send will do nothing on an input port + void sendMidi (byte[] msg, int offset, int count); + } + + //============================================================================== + public static class BluetoothManager extends ScanCallback + { + BluetoothManager (Context contextToUse) + { + appContext = contextToUse; + } + + public String[] getMidiBluetoothAddresses () + { + return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size ()]); + } + + public String getHumanReadableStringForBluetoothAddress (String address) + { + BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter ().getRemoteDevice (address); + return btDevice.getName (); + } + + public int getBluetoothDeviceStatus (String address) + { + return getAndroidMidiDeviceManager (appContext).getBluetoothDeviceStatus (address); + } + + public void startStopScan (boolean shouldStart) + { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter (); + + if (bluetoothAdapter == null) + { + Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter"); + return; + } + + BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner (); + + if (bluetoothLeScanner == null) + { + Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner"); + return; + } + + if (shouldStart) + { + ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder (); + scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID)); + + ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder (); + scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER) + .setScanMode (ScanSettings.MATCH_MODE_STICKY); + + bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build ()), + scanSettingsBuilder.build (), + this); + } else + { + bluetoothLeScanner.stopScan (this); + } + } + + public boolean pairBluetoothMidiDevice (String address) + { + BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter ().getRemoteDevice (address); + + if (btDevice == null) + { + Log.d ("JUCE", "failed to create buletooth device from address"); + return false; + } + + return getAndroidMidiDeviceManager (appContext).pairBluetoothDevice (btDevice); + } + + public void unpairBluetoothMidiDevice (String address) + { + getAndroidMidiDeviceManager (appContext).unpairBluetoothDevice (address); + } + + public void onScanFailed (int errorCode) + { + } + + public void onScanResult (int callbackType, ScanResult result) + { + if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES + || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH) + { + BluetoothDevice device = result.getDevice (); + + if (device != null) + bluetoothMidiDevices.add (device.getAddress ()); + } + + if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) + { + Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST"); + BluetoothDevice device = result.getDevice (); + + if (device != null) + { + bluetoothMidiDevices.remove (device.getAddress ()); + unpairBluetoothMidiDevice (device.getAddress ()); + } + } + } + + public void onBatchScanResults (List results) + { + for (ScanResult result : results) + onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result); + } + + private BluetoothLeScanner scanner; + private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700"; + + private HashSet bluetoothMidiDevices = new HashSet (); + private Context appContext = null; + } + + public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort + { + private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp); + + public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse) + { + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + juceHost = hostToUse; + isConnected = false; + } + + @Override + protected void finalize () throws Throwable + { + close (); + super.finalize (); + } + + @Override + public boolean isInputPort () + { + return true; + } + + @Override + public void start () + { + if (owner != null && androidPort != null && !isConnected) + { + androidPort.connect (this); + isConnected = true; + } + } + + @Override + public void stop () + { + if (owner != null && androidPort != null && isConnected) + { + androidPort.disconnect (this); + isConnected = false; + } + } + + @Override + public void close () + { + if (androidPort != null) + { + try + { + androidPort.close (); + } catch (IOException exception) + { + Log.d ("JUCE", "IO Exception while closing port"); + } + } + + if (owner != null) + owner.removePort (portPath); + + owner = null; + androidPort = null; + } + + @Override + public void onSend (byte[] msg, int offset, int count, long timestamp) + { + if (count > 0) + handleReceive (juceHost, msg, offset, count, timestamp); + } + + @Override + public void onFlush () + {} + + @Override + public void sendMidi (byte[] msg, int offset, int count) + { + } + + MidiDeviceManager owner; + MidiOutputPort androidPort; + MidiPortPath portPath; + long juceHost; + boolean isConnected; + } + + public static class JuceMidiOutputPort implements JuceMidiPort + { + public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse) + { + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + } + + @Override + protected void finalize () throws Throwable + { + close (); + super.finalize (); + } + + @Override + public boolean isInputPort () + { + return false; + } + + @Override + public void start () + { + } + + @Override + public void stop () + { + } + + @Override + public void sendMidi (byte[] msg, int offset, int count) + { + if (androidPort != null) + { + try + { + androidPort.send (msg, offset, count); + } catch (IOException exception) + { + Log.d ("JUCE", "send midi had IO exception"); + } + } + } + + @Override + public void close () + { + if (androidPort != null) + { + try + { + androidPort.close (); + } catch (IOException exception) + { + Log.d ("JUCE", "IO Exception while closing port"); + } + } + + if (owner != null) + owner.removePort (portPath); + + owner = null; + androidPort = null; + } + + MidiDeviceManager owner; + MidiInputPort androidPort; + MidiPortPath portPath; + } + + private static class MidiPortPath extends Object + { + public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex) + { + deviceId = deviceIdToUse; + isInput = direction; + portIndex = androidIndex; + + } + + public int deviceId; + public int portIndex; + public boolean isInput; + + @Override + public int hashCode () + { + Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127)); + return i.hashCode () * (isInput ? -1 : 1); + } + + @Override + public boolean equals (Object obj) + { + if (obj == null) + return false; + + if (getClass () != obj.getClass ()) + return false; + + MidiPortPath other = (MidiPortPath) obj; + return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId); + } + } + + //============================================================================== + public static class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener + { + //============================================================================== + private class DummyBluetoothGattCallback extends BluetoothGattCallback + { + public DummyBluetoothGattCallback (MidiDeviceManager mm) + { + super (); + owner = mm; + } + + public void onConnectionStateChange (BluetoothGatt gatt, int status, int newState) + { + if (newState == BluetoothProfile.STATE_CONNECTED) + { + gatt.requestConnectionPriority (BluetoothGatt.CONNECTION_PRIORITY_HIGH); + owner.pairBluetoothDeviceStepTwo (gatt.getDevice ()); + } + } + + public void onServicesDiscovered (BluetoothGatt gatt, int status) {} + + public void onCharacteristicRead (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {} + + public void onCharacteristicWrite (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {} + + public void onCharacteristicChanged (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {} + + public void onDescriptorRead (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {} + + public void onDescriptorWrite (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {} + + public void onReliableWriteCompleted (BluetoothGatt gatt, int status) {} + + public void onReadRemoteRssi (BluetoothGatt gatt, int rssi, int status) {} + + public void onMtuChanged (BluetoothGatt gatt, int mtu, int status) {} + + private MidiDeviceManager owner; + } + + //============================================================================== + private class MidiDeviceOpenTask extends java.util.TimerTask + { + public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse) + { + owner = deviceManager; + midiDevice = device; + btGatt = gattToUse; + } + + @Override + public boolean cancel () + { + synchronized (MidiDeviceOpenTask.class) + { + owner = null; + boolean retval = super.cancel (); + + if (btGatt != null) + { + btGatt.disconnect (); + btGatt.close (); + + btGatt = null; + } + + if (midiDevice != null) + { + try + { + midiDevice.close (); + } catch (IOException e) + { + } + + midiDevice = null; + } + + return retval; + } + } + + public String getBluetoothAddress () + { + synchronized (MidiDeviceOpenTask.class) + { + if (midiDevice != null) + { + MidiDeviceInfo info = midiDevice.getInfo (); + if (info.getType () == MidiDeviceInfo.TYPE_BLUETOOTH) + { + BluetoothDevice btDevice = (BluetoothDevice) info.getProperties ().get (info.PROPERTY_BLUETOOTH_DEVICE); + if (btDevice != null) + return btDevice.getAddress (); + } + } + } + + return ""; + } + + public BluetoothGatt getGatt () { return btGatt; } + + public int getID () + { + return midiDevice.getInfo ().getId (); + } + + @Override + public void run () + { + synchronized (MidiDeviceOpenTask.class) + { + if (owner != null && midiDevice != null) + owner.onDeviceOpenedDelayed (midiDevice); + } + } + + private MidiDeviceManager owner; + private MidiDevice midiDevice; + private BluetoothGatt btGatt; + } + + //============================================================================== + public MidiDeviceManager (Context contextToUse) + { + appContext = contextToUse; + manager = (MidiManager) appContext.getSystemService (MIDI_SERVICE); + + if (manager == null) + { + Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service"); + return; + } + + openPorts = new HashMap> (); + midiDevices = new ArrayList> (); + openTasks = new HashMap (); + btDevicesPairing = new HashMap (); + + MidiDeviceInfo[] foundDevices = manager.getDevices (); + for (MidiDeviceInfo info : foundDevices) + onDeviceAdded (info); + + manager.registerDeviceCallback (this, null); + } + + protected void finalize () throws Throwable + { + manager.unregisterDeviceCallback (this); + + synchronized (MidiDeviceManager.class) + { + btDevicesPairing.clear (); + + for (Integer deviceID : openTasks.keySet ()) + openTasks.get (deviceID).cancel (); + + openTasks = null; + } + + for (MidiPortPath key : openPorts.keySet ()) + openPorts.get (key).get ().close (); + + openPorts = null; + + for (Pair device : midiDevices) + { + if (device.second != null) + { + device.second.disconnect (); + device.second.close (); + } + + device.first.close (); + } + + midiDevices.clear (); + + super.finalize (); + } + + public String[] getJuceAndroidMidiInputDevices () + { + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + } + + public String[] getJuceAndroidMidiOutputDevices () + { + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); + } + + private String[] getJuceAndroidMidiDevices (int portType) + { + // only update the list when JUCE asks for a new list + synchronized (MidiDeviceManager.class) + { + deviceInfos = getDeviceInfos (); + } + + ArrayList portNames = new ArrayList (); + + int index = 0; + for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index)) + portNames.add (getPortName (portInfo)); + + String[] names = new String[portNames.size ()]; + return portNames.toArray (names); + } + + private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput) + { + synchronized (MidiDeviceManager.class) + { + int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT); + MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index); + + if (portInfo != null) + { + // ports must be opened exclusively! + if (openPorts.containsKey (portInfo)) + return null; + + Pair devicePair = getMidiDevicePairForId (portInfo.deviceId); + + if (devicePair != null) + { + MidiDevice device = devicePair.first; + if (device != null) + { + JuceMidiPort juceMidiPort = null; + + if (isInput) + { + MidiOutputPort outputPort = device.openOutputPort (portInfo.portIndex); + + if (outputPort != null) + juceMidiPort = new JuceMidiInputPort (this, outputPort, portInfo, host); + } else + { + MidiInputPort inputPort = device.openInputPort (portInfo.portIndex); + + if (inputPort != null) + juceMidiPort = new JuceMidiOutputPort (this, inputPort, portInfo); + } + + if (juceMidiPort != null) + { + openPorts.put (portInfo, new WeakReference (juceMidiPort)); + + return juceMidiPort; + } + } + } + } + } + + return null; + } + + public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) + { + return openMidiPortWithJuceIndex (index, host, true); + } + + public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) + { + return openMidiPortWithJuceIndex (index, 0, false); + } + + /* 0: unpaired, 1: paired, 2: pairing */ + public int getBluetoothDeviceStatus (String address) + { + synchronized (MidiDeviceManager.class) + { + if (!address.isEmpty ()) + { + if (findMidiDeviceForBluetoothAddress (address) != null) + return 1; + + if (btDevicesPairing.containsKey (address)) + return 2; + + if (findOpenTaskForBluetoothAddress (address) != null) + return 2; + } + } + + return 0; + } + + public boolean pairBluetoothDevice (BluetoothDevice btDevice) + { + String btAddress = btDevice.getAddress (); + if (btAddress.isEmpty ()) + return false; + + synchronized (MidiDeviceManager.class) + { + if (getBluetoothDeviceStatus (btAddress) != 0) + return false; + + + btDevicesPairing.put (btDevice.getAddress (), null); + BluetoothGatt gatt = btDevice.connectGatt (appContext.getApplicationContext (), true, new DummyBluetoothGattCallback (this)); + + if (gatt != null) + { + btDevicesPairing.put (btDevice.getAddress (), gatt); + } else + { + pairBluetoothDeviceStepTwo (btDevice); + } + } + + return true; + } + + public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice) + { + manager.openBluetoothDevice (btDevice, this, null); + } + + public void unpairBluetoothDevice (String address) + { + if (address.isEmpty ()) + return; + + synchronized (MidiDeviceManager.class) + { + if (btDevicesPairing.containsKey (address)) + { + BluetoothGatt gatt = btDevicesPairing.get (address); + if (gatt != null) + { + gatt.disconnect (); + gatt.close (); + } + + btDevicesPairing.remove (address); + } + + MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address); + if (openTask != null) + { + int deviceID = openTask.getID (); + openTask.cancel (); + openTasks.remove (deviceID); + } + + Pair midiDevicePair = findMidiDeviceForBluetoothAddress (address); + if (midiDevicePair != null) + { + MidiDevice midiDevice = midiDevicePair.first; + onDeviceRemoved (midiDevice.getInfo ()); + + try + { + midiDevice.close (); + } catch (IOException exception) + { + Log.d ("JUCE", "IOException while closing midi device"); + } + } + } + } + + private Pair findMidiDeviceForBluetoothAddress (String address) + { + for (Pair midiDevice : midiDevices) + { + MidiDeviceInfo info = midiDevice.first.getInfo (); + if (info.getType () == MidiDeviceInfo.TYPE_BLUETOOTH) + { + BluetoothDevice btDevice = (BluetoothDevice) info.getProperties ().get (info.PROPERTY_BLUETOOTH_DEVICE); + if (btDevice != null && btDevice.getAddress ().equals (address)) + return midiDevice; + } + } + + return null; + } + + private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address) + { + for (Integer deviceID : openTasks.keySet ()) + { + MidiDeviceOpenTask openTask = openTasks.get (deviceID); + if (openTask.getBluetoothAddress ().equals (address)) + return openTask; + } + + return null; + } + + public void removePort (MidiPortPath path) + { + openPorts.remove (path); + } + + public String getInputPortNameForJuceIndex (int index) + { + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); + if (portInfo != null) + return getPortName (portInfo); + + return ""; + } + + public String getOutputPortNameForJuceIndex (int index) + { + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); + if (portInfo != null) + return getPortName (portInfo); + + return ""; + } + + public void onDeviceAdded (MidiDeviceInfo info) + { + // only add standard midi devices + if (info.getType () == info.TYPE_BLUETOOTH) + return; + + manager.openDevice (info, this, null); + } + + public void onDeviceRemoved (MidiDeviceInfo info) + { + synchronized (MidiDeviceManager.class) + { + Pair devicePair = getMidiDevicePairForId (info.getId ()); + + if (devicePair != null) + { + MidiDevice midiDevice = devicePair.first; + BluetoothGatt gatt = devicePair.second; + + // close all ports that use this device + boolean removedPort = true; + + while (removedPort == true) + { + removedPort = false; + for (MidiPortPath key : openPorts.keySet ()) + { + if (key.deviceId == info.getId ()) + { + openPorts.get (key).get ().close (); + removedPort = true; + break; + } + } + } + + if (gatt != null) + { + gatt.disconnect (); + gatt.close (); + } + + midiDevices.remove (devicePair); + } + } + } + + public void onDeviceStatusChanged (MidiDeviceStatus status) + { + } + + @Override + public void onDeviceOpened (MidiDevice theDevice) + { + synchronized (MidiDeviceManager.class) + { + MidiDeviceInfo info = theDevice.getInfo (); + int deviceID = info.getId (); + BluetoothGatt gatt = null; + boolean isBluetooth = false; + + if (!openTasks.containsKey (deviceID)) + { + if (info.getType () == MidiDeviceInfo.TYPE_BLUETOOTH) + { + isBluetooth = true; + BluetoothDevice btDevice = (BluetoothDevice) info.getProperties ().get (info.PROPERTY_BLUETOOTH_DEVICE); + if (btDevice != null) + { + String btAddress = btDevice.getAddress (); + if (btDevicesPairing.containsKey (btAddress)) + { + gatt = btDevicesPairing.get (btAddress); + btDevicesPairing.remove (btAddress); + } else + { + // unpair was called in the mean time + try + { + Pair midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress ()); + if (midiDevicePair != null) + { + gatt = midiDevicePair.second; + + if (gatt != null) + { + gatt.disconnect (); + gatt.close (); + } + } + + theDevice.close (); + } catch (IOException e) + { + } + + return; + } + } + } + + MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt); + openTasks.put (deviceID, openTask); + + new java.util.Timer ().schedule (openTask, (isBluetooth ? 2000 : 100)); + } + } + } + + public void onDeviceOpenedDelayed (MidiDevice theDevice) + { + synchronized (MidiDeviceManager.class) + { + int deviceID = theDevice.getInfo ().getId (); + + if (openTasks.containsKey (deviceID)) + { + if (!midiDevices.contains (theDevice)) + { + BluetoothGatt gatt = openTasks.get (deviceID).getGatt (); + openTasks.remove (deviceID); + midiDevices.add (new Pair (theDevice, gatt)); + } + } else + { + // unpair was called in the mean time + MidiDeviceInfo info = theDevice.getInfo (); + BluetoothDevice btDevice = (BluetoothDevice) info.getProperties ().get (info.PROPERTY_BLUETOOTH_DEVICE); + if (btDevice != null) + { + String btAddress = btDevice.getAddress (); + Pair midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress ()); + if (midiDevicePair != null) + { + BluetoothGatt gatt = midiDevicePair.second; + + if (gatt != null) + { + gatt.disconnect (); + gatt.close (); + } + } + } + + try + { + theDevice.close (); + } catch (IOException e) + { + } + } + } + } + + public String getPortName (MidiPortPath path) + { + int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + + synchronized (MidiDeviceManager.class) + { + for (MidiDeviceInfo info : deviceInfos) + { + int localIndex = 0; + if (info.getId () == path.deviceId) + { + for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ()) + { + int portType = portInfo.getType (); + if (portType == portTypeToFind) + { + int portIndex = portInfo.getPortNumber (); + if (portIndex == path.portIndex) + { + String portName = portInfo.getName (); + if (portName.isEmpty ()) + portName = (String) info.getProperties ().get (info.PROPERTY_NAME); + + return portName; + } + } + } + } + } + } + + return ""; + } + + public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex) + { + int portIdx = 0; + for (MidiDeviceInfo info : deviceInfos) + { + for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ()) + { + if (portInfo.getType () == portType) + { + if (portIdx == juceIndex) + return new MidiPortPath (info.getId (), + (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT), + portInfo.getPortNumber ()); + + portIdx++; + } + } + } + + return null; + } + + private MidiDeviceInfo[] getDeviceInfos () + { + synchronized (MidiDeviceManager.class) + { + MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size ()]; + + int idx = 0; + for (Pair midiDevice : midiDevices) + infos[idx++] = midiDevice.first.getInfo (); + + return infos; + } + } + + private Pair getMidiDevicePairForId (int deviceId) + { + synchronized (MidiDeviceManager.class) + { + for (Pair midiDevice : midiDevices) + if (midiDevice.first.getInfo ().getId () == deviceId) + return midiDevice; + } + + return null; + } + + private MidiManager manager; + private HashMap btDevicesPairing; + private HashMap openTasks; + private ArrayList> midiDevices; + private MidiDeviceInfo[] deviceInfos; + private HashMap> openPorts; + private Context appContext = null; + } + + public static MidiDeviceManager getAndroidMidiDeviceManager (Context context) + { + if (context.getSystemService (MIDI_SERVICE) == null) + return null; + + synchronized (JuceMidiSupport.class) + { + if (midiDeviceManager == null) + midiDeviceManager = new MidiDeviceManager (context); + } + + return midiDeviceManager; + } + + public static BluetoothManager getAndroidBluetoothManager (Context context) + { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter (); + + if (adapter == null) + return null; + + if (adapter.getBluetoothLeScanner () == null) + return null; + + synchronized (JuceMidiSupport.class) + { + if (bluetoothManager == null) + bluetoothManager = new BluetoothManager (context); + } + + return bluetoothManager; + } + + private static MidiDeviceManager midiDeviceManager = null; + private static BluetoothManager bluetoothManager = null; +} \ No newline at end of file diff --git a/modules/juce_audio_devices/native/juce_android_Audio.cpp b/modules/juce_audio_devices/native/juce_android_Audio.cpp index fe995ae433..3fa7399259 100644 --- a/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ b/modules/juce_audio_devices/native/juce_android_Audio.cpp @@ -23,7 +23,7 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \ METHOD (constructor, "", "(IIIIII)V") \ @@ -38,7 +38,7 @@ DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack") #undef JNI_CLASS_MEMBERS //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ METHOD (constructor, "", "(IIIII)V") \ METHOD (getState, "getState", "()I") \ @@ -50,13 +50,6 @@ DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack") DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord") #undef JNI_CLASS_MEMBERS -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - STATICFIELD (SDK_INT, "SDK_INT", "I") \ - - DECLARE_JNI_CLASS (AndroidBuildVersion, "android/os/Build$VERSION") -#undef JNI_CLASS_MEMBERS - //============================================================================== enum { @@ -198,11 +191,11 @@ public: if (numClientOutputChannels > 0) { numDeviceOutputChannels = 2; - outputDevice = GlobalRef (env->NewObject (AudioTrack, AudioTrack.constructor, - STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, - (jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast (sizeof (int16))), MODE_STREAM)); + outputDevice = GlobalRef (LocalRef(env->NewObject (AudioTrack, AudioTrack.constructor, + STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, + (jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast (sizeof (int16))), MODE_STREAM))); - const bool supportsUnderrunCount = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24); + const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24); getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0; int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState); @@ -232,11 +225,11 @@ public: else { numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); - inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, - 0 /* (default audio source) */, sampleRate, - numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, - ENCODING_PCM_16BIT, - (jint) (minBufferSizeIn * numDeviceInputChannels * static_cast (sizeof (int16))))); + inputDevice = GlobalRef (LocalRef(env->NewObject (AudioRecord, AudioRecord.constructor, + 0 /* (default audio source) */, sampleRate, + numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, + ENCODING_PCM_16BIT, + (jint) (minBufferSizeIn * numDeviceInputChannels * static_cast (sizeof (int16)))))); int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState); if (inputDeviceState > 0) diff --git a/modules/juce_audio_devices/native/juce_android_Midi.cpp b/modules/juce_audio_devices/native/juce_android_Midi.cpp index a178abf01f..449ff1c31f 100644 --- a/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_android_Midi.cpp @@ -23,24 +23,346 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +//============================================================================== +// This byte-code is generated from native/java/com/roli/juce/JuceMidiSupport.java with min sdk version 23 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const uint8 javaMidiByteCode[] = +{31,139,8,8,173,175,226,91,0,3,106,117,99,101,95,97,117,100,105,111,95,100,101,118,105,99,101,115,46,100,101,120,0,149, +124,11,124,220,69,181,255,153,223,99,119,179,217,164,155,77,218,164,105,178,217,164,73,179,133,60,155,182,164,77,26,154, +164,105,155,118,251,160,217,22,105,144,186,77,182,237,150,100,55,236,110,250,0,175,148,135,180,32,42,42,143,42,92,254,40, +175,130,168,232,5,46,34,42,8,87,20,69,184,92,212,122,125,1,194,223,130,200,67,65,68,196,222,239,153,153,125,164,45,84, +203,231,187,103,126,103,206,156,153,57,115,230,204,153,223,46,25,141,238,113,183,117,44,160,7,183,254,71,91,73,96,244, +225,231,218,251,238,107,108,49,191,55,239,138,244,25,191,14,80,247,85,235,137,38,136,104,207,166,249,62,210,255,30,90,71, +116,150,80,252,1,224,25,155,104,19,232,17,7,81,0,244,117,55,209,93,76,11,137,10,64,215,151,128,223,3,121,104,216,209,76, +180,19,24,7,146,192,36,112,61,112,35,112,8,184,27,184,7,120,0,120,8,120,26,40,110,33,90,7,156,11,196,129,36,176,27,56,8, +124,23,248,30,240,3,224,151,192,107,192,123,64,105,43,209,12,160,18,168,1,234,129,83,128,22,160,3,88,4,108,1,46,5,110,7, +158,6,204,54,162,54,96,24,216,7,124,25,248,9,240,6,224,111,39,234,7,206,1,46,4,190,2,60,13,188,3,84,207,35,90,11,236,7, +238,0,126,2,188,1,56,58,136,170,128,5,192,32,240,97,32,9,252,16,120,23,104,153,79,180,21,184,10,248,13,80,191,128,232,76, +224,227,192,109,192,227,192,203,64,209,66,162,38,96,37,176,5,72,2,251,129,235,129,175,1,223,7,94,0,204,211,136,102,2,109, +192,48,144,0,46,1,14,2,119,1,15,3,63,7,142,0,127,3,90,59,137,150,2,67,192,78,224,223,128,135,128,103,128,231,129,130,69, +68,30,192,11,76,7,102,1,179,129,70,160,9,152,7,44,2,122,128,126,96,16,88,7,108,2,62,12,68,128,81,96,7,48,6,76,0,87,3,143, +0,47,1,255,0,74,23,195,55,128,38,96,1,176,24,232,7,206,0,206,1,118,2,73,224,82,224,38,224,30,224,251,192,175,128,87,129, +119,0,179,11,227,3,170,129,122,96,30,176,24,88,5,156,1,124,8,136,0,113,96,15,240,113,224,83,192,13,192,205,192,109,192, +55,128,7,128,71,129,159,0,191,6,94,6,222,1,236,110,216,0,168,2,234,129,86,96,49,176,19,72,3,215,1,247,3,223,5,158,0,126, +1,252,22,120,25,120,13,120,23,40,95,66,52,31,88,13,124,4,56,31,248,12,112,61,240,85,224,155,192,15,129,159,2,191,1,10, +176,95,188,64,121,143,218,59,61,192,89,192,94,224,11,192,189,192,19,192,207,128,183,0,227,116,162,18,224,20,96,62,208,15, +132,129,109,64,2,248,24,176,31,248,12,112,29,240,37,224,86,224,78,224,235,192,127,2,223,1,126,8,252,12,248,29,240,10,240, +22,96,44,133,31,0,51,128,90,160,21,152,15,116,3,43,129,51,128,51,129,17,96,23,112,25,240,57,224,58,224,6,224,139,192,237, +192,87,129,251,129,239,0,143,0,63,2,126,11,252,21,40,238,37,106,0,122,128,245,192,8,144,4,46,6,62,15,220,9,60,12,60,14, +28,6,94,6,172,62,34,31,16,0,130,64,15,48,4,108,1,226,192,167,128,175,1,143,3,255,11,188,8,188,6,252,13,16,253,216,87,64, +25,48,23,24,0,206,1,70,129,113,96,15,112,49,112,37,112,29,240,69,224,110,224,1,224,113,224,73,224,247,192,107,192,187, +128,107,25,246,6,112,42,48,31,232,6,250,128,21,192,102,32,1,236,1,46,6,174,0,174,2,238,2,238,3,30,3,158,1,94,0,222,0,222, +6,254,1,20,32,184,86,0,1,96,14,208,6,116,1,189,192,32,16,6,62,12,164,129,79,3,119,0,119,3,211,16,115,203,128,58,96,54,80, +15,52,0,115,128,70,32,8,204,5,78,1,78,5,154,0,132,89,66,216,36,132,65,66,184,35,132,53,66,8,35,132,41,66,104,34,132,33, +66,136,33,132,13,66,104,32,108,93,194,214,35,108,15,130,123,19,92,150,224,42,132,101,33,152,151,96,18,90,166,207,135,229, +192,10,96,37,48,8,172,2,86,3,33,96,13,176,22,88,199,231,4,112,6,176,1,24,2,194,164,206,149,15,1,155,129,179,129,115,128, +45,64,4,24,1,70,129,40,112,46,112,1,240,49,224,66,96,31,112,17,112,49,112,9,41,155,100,254,121,53,125,22,147,47,209,229, +35,40,87,130,26,250,153,203,166,46,215,232,242,179,90,198,210,252,90,93,126,93,243,93,121,242,56,2,233,239,154,95,168, +249,179,128,34,110,211,164,248,197,121,125,77,203,43,251,242,228,203,180,60,151,43,242,218,86,230,245,85,165,199,198,50, +126,45,83,163,203,204,175,209,114,30,173,167,78,203,84,235,114,89,147,146,229,114,149,110,91,159,215,182,65,183,229,126, +216,135,130,122,12,45,121,227,108,205,27,91,91,222,216,184,220,214,164,242,2,46,119,54,229,248,25,123,182,231,233,105, +207,27,63,151,151,230,149,51,242,157,121,250,217,15,87,234,126,23,107,62,251,194,18,93,30,211,101,214,57,174,203,235,81, +142,235,242,135,80,78,232,242,104,147,202,105,184,156,70,121,183,46,127,20,229,243,116,249,0,202,73,93,190,10,229,73,93, +190,1,229,93,186,124,75,94,249,238,60,157,15,230,149,61,121,229,71,243,202,63,206,235,247,153,60,254,179,121,229,35,121, +253,190,158,199,255,107,94,91,222,208,123,50,125,53,231,228,43,80,222,171,203,129,230,92,219,165,121,122,218,242,244,119, +230,143,225,212,92,185,169,57,215,215,124,148,211,25,61,40,159,175,203,43,155,115,182,90,143,114,74,151,207,110,86,123, +181,71,175,209,71,117,153,215,232,223,116,57,157,87,110,203,43,103,124,160,87,183,229,114,95,158,63,244,231,249,195,50, +205,159,165,203,150,244,243,54,186,143,20,93,42,184,205,52,250,132,108,219,78,159,146,244,52,250,140,164,46,234,17,236, +183,21,116,5,219,10,189,191,36,169,160,87,37,109,160,217,178,126,14,53,11,142,5,101,82,174,86,243,107,53,127,182,126,102, +122,134,224,125,101,209,181,196,212,75,127,145,84,213,215,235,250,6,61,158,6,68,219,107,36,237,163,59,37,45,167,55,37, +157,79,239,232,122,191,80,52,32,212,190,60,68,76,123,232,15,164,158,231,10,142,247,53,116,21,49,109,160,183,137,227,155, +139,190,39,169,73,143,74,106,211,255,18,199,55,39,221,40,105,29,61,168,233,255,176,205,112,82,220,160,233,87,37,181,232, +191,36,93,67,11,161,223,6,223,73,28,251,86,208,42,193,116,1,173,21,156,247,43,190,59,75,221,116,157,164,5,180,28,245,30, +173,167,72,215,23,129,115,157,164,133,180,76,40,58,32,56,46,22,209,119,137,105,45,253,146,56,118,171,241,120,17,61,127, +44,233,52,154,41,152,122,169,90,112,60,87,227,230,184,254,51,77,127,69,42,166,254,72,210,51,232,176,164,37,244,11,205, +231,250,50,173,183,12,167,83,47,244,76,215,227,42,199,105,244,152,164,173,84,46,152,46,162,10,73,187,105,158,164,93,180, +73,112,108,86,237,43,224,225,255,174,41,219,107,166,214,83,137,241,127,139,56,134,250,232,126,226,216,107,208,45,210,15, +151,201,250,26,172,163,162,130,30,150,180,129,126,40,105,152,254,91,210,229,100,74,127,61,133,74,37,61,149,202,36,93,79, +115,36,29,164,213,146,14,208,70,233,151,167,75,125,1,61,46,166,255,33,105,51,253,90,210,16,189,162,249,211,164,252,106, +154,46,233,42,234,23,138,63,168,233,26,233,207,61,82,95,173,214,87,171,245,213,106,61,181,186,93,173,110,87,171,219,213, +105,249,58,45,87,167,229,234,180,92,157,150,155,77,75,165,254,217,200,58,44,249,220,65,182,166,14,73,219,201,41,233,124, +114,105,90,160,249,94,77,75,36,109,35,159,166,51,228,190,234,149,122,235,209,255,231,37,173,163,135,36,117,208,15,72,157, +115,143,75,58,151,186,244,126,114,202,253,165,230,215,0,143,184,71,210,153,116,175,164,106,125,26,224,23,223,151,116,54, +61,33,233,70,250,137,164,97,122,82,211,167,36,45,165,167,181,220,51,146,214,211,79,37,157,69,63,151,180,147,220,178,223, +211,168,80,83,143,80,252,34,73,23,83,177,80,251,191,82,210,25,52,75,210,10,170,146,116,45,213,75,218,66,13,66,197,139, +249,146,14,80,88,198,133,38,57,159,57,200,176,190,166,227,194,111,101,60,152,11,11,40,234,148,116,58,125,91,210,74,250, +14,241,121,126,138,228,183,106,249,86,120,236,135,4,159,219,74,190,77,219,167,13,30,253,8,241,249,172,244,183,195,206,47, +19,231,141,253,82,174,3,30,206,126,63,95,183,155,15,185,253,250,249,122,253,252,255,36,157,67,127,212,207,29,66,229,156, +43,37,29,162,33,193,249,103,35,93,73,156,131,42,61,11,117,251,133,144,191,73,210,26,217,207,66,100,183,127,146,52,64,237, +66,241,89,223,105,186,221,105,186,255,211,116,63,167,233,126,78,211,253,116,98,252,191,33,166,200,136,4,231,23,106,92, +139,53,237,210,122,186,144,205,158,46,56,247,85,207,221,218,191,248,12,2,91,190,247,32,185,255,113,94,115,162,141,36,247, +189,53,42,199,18,142,92,142,196,245,46,156,105,11,215,170,231,128,110,207,252,167,78,85,180,2,116,173,174,175,213,245, +109,121,245,109,160,151,233,250,217,90,175,157,167,127,37,234,191,170,235,235,53,191,59,175,254,67,168,127,81,215,55,104, +253,211,129,195,90,255,14,80,207,58,85,63,71,183,203,31,255,221,168,63,160,235,27,243,198,151,169,127,8,245,55,233,122, +206,175,127,129,196,255,217,144,146,251,131,166,127,11,229,234,10,215,228,202,229,107,84,125,93,30,239,84,93,94,8,186,36, +175,188,114,141,202,211,89,102,8,229,115,116,219,152,166,231,107,250,9,77,111,210,244,94,77,127,156,215,199,111,53,239, +101,169,211,144,229,111,14,168,187,195,132,183,8,207,117,240,153,9,47,251,238,176,215,66,84,31,246,26,52,236,51,112,46, +177,60,235,121,124,64,229,254,97,212,156,231,189,148,248,212,139,7,118,96,141,221,50,223,183,180,220,127,15,168,123,193, +121,178,23,143,136,7,12,236,35,200,122,109,249,204,113,205,68,29,203,254,102,64,157,105,225,128,69,225,90,11,50,95,66, +141,91,204,46,89,6,221,55,99,124,30,248,224,50,41,99,203,83,30,119,121,180,153,1,154,244,222,134,62,61,34,233,189,133, +219,24,157,70,17,120,183,162,204,109,60,228,243,197,219,22,193,131,130,175,23,235,145,17,189,51,160,236,192,119,21,135, +156,25,158,151,171,187,160,175,100,94,153,77,190,218,142,178,18,140,163,4,253,121,176,111,10,41,220,206,227,226,155,145, +199,136,7,110,130,207,250,122,59,202,106,16,191,166,83,165,177,147,206,11,180,131,151,107,225,59,166,197,23,101,173,165, +109,209,141,8,90,44,231,194,125,7,150,171,187,74,190,173,122,161,101,17,250,85,250,191,161,245,251,196,52,17,110,87,150, +23,82,242,124,105,169,224,91,110,104,98,237,109,208,117,38,207,163,220,231,208,250,160,199,77,149,22,244,216,69,82,79,24, +125,199,229,133,209,35,22,137,76,157,71,215,5,255,212,89,176,128,234,12,55,60,129,109,86,105,89,232,175,141,173,108,197, +3,94,196,226,58,179,8,117,62,88,46,30,40,67,6,204,252,233,184,181,122,44,95,3,151,194,52,59,189,20,61,76,67,107,143,189, +198,182,28,231,121,175,83,237,189,165,104,229,177,227,75,11,169,247,227,193,111,199,3,30,220,104,131,223,164,172,127,141, +46,87,247,204,169,254,245,111,240,175,98,228,97,14,57,163,241,229,234,110,57,225,109,65,155,225,217,5,52,92,239,160,225, +6,55,109,158,227,130,229,207,14,56,229,218,218,210,191,4,93,180,92,197,16,159,25,238,117,80,167,112,18,211,184,247,84, +212,133,123,11,192,41,144,52,220,231,70,95,31,133,157,135,251,161,179,223,1,45,69,122,5,124,122,5,130,71,84,28,98,221,66, +52,227,248,18,114,76,215,162,15,142,153,113,126,193,69,9,239,229,218,191,212,46,35,186,113,185,218,135,62,88,69,104,222, +45,203,115,126,88,140,185,241,93,251,206,229,106,223,44,41,244,208,208,69,46,114,238,115,126,78,220,34,238,181,190,191, +203,181,84,203,90,250,182,254,64,94,123,67,239,165,239,47,87,113,46,236,45,80,94,232,197,140,33,177,209,235,148,126,192, +207,241,64,19,246,148,207,123,182,215,57,165,237,147,31,208,182,51,219,182,153,219,82,166,45,143,133,199,112,88,175,219, +132,247,0,71,15,225,161,97,163,144,134,225,41,197,217,117,56,146,183,14,133,122,29,10,97,177,217,114,29,60,122,29,60,88, +135,162,236,58,64,79,127,225,191,176,14,239,101,215,97,229,9,215,193,94,161,215,1,30,228,212,163,47,4,175,148,231,221, +174,71,5,26,95,138,44,43,154,235,183,79,232,126,223,86,239,71,116,191,238,204,90,214,173,200,173,69,134,23,204,227,153, +50,218,17,181,172,80,239,84,134,197,52,216,138,45,54,108,20,203,248,170,222,204,44,206,107,147,89,231,101,39,224,173,95, +145,31,195,44,57,167,179,87,168,56,234,11,116,216,211,176,190,241,64,1,180,134,3,188,219,93,188,239,16,47,46,144,183,140, +156,158,115,79,160,123,247,9,120,151,156,128,247,233,99,230,199,255,174,63,1,239,214,60,158,45,45,71,244,181,21,188,179, +217,14,165,176,195,157,210,14,56,111,204,18,26,182,120,132,150,180,162,73,15,173,80,239,100,170,140,122,170,54,124,98, +184,221,135,85,253,50,106,224,143,237,94,172,215,52,73,227,240,71,161,75,28,37,88,210,139,231,18,72,184,37,141,123,43,52, +191,132,252,6,238,96,194,111,52,138,34,17,124,155,103,51,19,117,53,114,124,166,204,23,28,210,103,231,94,216,220,52,87, +151,13,250,245,10,149,59,86,153,24,139,25,222,0,221,198,108,98,26,247,206,228,88,39,226,222,58,121,10,249,230,119,244, +205,0,183,150,163,180,81,105,125,22,254,220,138,200,201,103,146,137,189,228,202,158,12,126,179,4,168,196,242,5,255,94, +132,82,163,161,206,251,57,104,217,172,125,73,32,223,205,248,239,187,43,84,125,216,235,149,107,109,104,111,19,43,51,231, +178,26,141,140,155,94,245,214,78,157,203,106,15,58,87,242,253,14,115,16,152,131,8,183,249,56,39,167,112,27,230,130,252, +142,159,195,243,160,33,144,70,196,245,11,246,127,191,104,36,213,103,169,236,171,12,251,70,157,193,190,149,234,125,166, +207,154,240,54,192,114,195,225,50,26,222,84,6,139,148,81,165,249,22,180,148,227,230,227,49,106,140,114,26,222,80,14,62, +110,154,56,87,42,13,236,40,115,187,220,201,243,176,214,117,198,66,248,192,65,142,229,27,102,226,105,62,158,62,45,159,42, +166,212,85,78,121,154,46,245,197,189,115,216,242,84,107,250,140,5,243,220,124,15,199,105,155,68,28,59,108,24,88,91,41, +211,54,151,54,88,193,31,187,244,25,115,250,74,149,43,134,71,42,208,254,115,188,51,44,206,31,44,114,155,157,102,155,204, +31,44,57,110,63,133,17,176,106,76,175,180,169,153,91,97,211,183,160,99,232,247,71,245,10,155,149,182,90,225,161,236,10, +255,226,168,94,97,51,30,248,4,206,72,214,252,228,209,18,195,103,4,255,225,200,156,117,43,213,187,234,112,95,37,244,223, +192,243,48,146,222,219,53,189,5,148,91,205,146,227,49,164,230,86,10,247,67,54,112,189,28,75,13,214,48,238,253,136,28,1, +247,50,36,229,95,56,90,34,124,34,248,15,142,51,202,123,46,92,169,222,123,87,217,13,84,109,135,211,60,235,107,120,182,214, +236,190,62,142,243,105,101,7,204,217,33,100,38,229,64,93,167,53,67,246,236,128,246,26,211,79,135,17,239,194,184,194,212, +88,202,26,124,158,175,177,12,33,68,240,247,126,187,196,40,178,252,118,163,197,126,209,34,123,109,37,151,244,19,131,238, +91,169,238,62,170,255,9,111,140,117,90,195,163,51,200,111,215,153,106,181,77,140,35,222,118,30,237,194,220,59,141,82,204, +113,2,123,128,51,139,171,112,235,58,12,7,207,212,6,223,80,61,241,28,76,158,67,47,103,137,215,194,59,61,86,141,133,44,81, +142,129,163,65,67,118,255,240,205,88,237,148,63,106,155,87,25,24,139,17,238,85,51,103,11,242,188,161,79,116,138,10,57, +111,83,90,220,47,111,156,53,66,205,89,230,92,222,26,153,115,117,156,113,228,168,223,224,88,226,163,224,123,42,154,168, +125,210,40,123,10,18,231,130,220,175,99,16,209,141,251,116,163,79,119,167,183,140,124,115,121,54,55,20,121,176,94,107,41, +252,240,76,204,224,11,200,143,185,119,39,44,225,119,151,144,111,102,240,213,13,237,179,104,34,16,199,125,212,227,236,116, +46,162,240,46,140,197,129,168,231,232,128,84,39,124,113,67,123,53,218,206,66,46,204,118,43,64,6,31,32,231,195,214,11,187, +28,242,77,39,191,107,129,246,58,171,3,122,62,137,53,139,183,253,59,181,91,126,119,240,41,140,216,221,40,84,251,74,110, +239,234,116,189,116,180,14,103,220,196,210,21,244,80,103,240,121,191,27,51,123,144,228,157,186,31,51,226,239,56,84,246, +178,66,250,22,123,241,126,204,141,223,253,249,28,225,20,118,90,224,20,248,122,149,133,121,90,225,212,116,216,236,243,124, +2,165,148,127,155,210,202,183,203,12,144,173,109,75,127,45,151,214,182,165,63,183,42,89,248,119,169,244,58,246,239,45, +144,15,190,84,100,250,173,70,211,103,14,163,222,15,221,51,143,213,152,183,131,43,243,118,240,28,146,178,208,56,91,106, +236,64,187,79,201,118,53,102,29,228,214,178,246,223,13,239,154,9,185,19,69,131,186,140,46,229,11,166,160,78,68,3,166,53, +166,37,79,29,51,239,201,33,159,50,17,163,130,117,255,82,233,253,12,202,156,141,112,214,196,247,232,185,176,223,124,82, +119,32,129,155,165,186,11,24,244,237,65,229,159,190,64,149,128,21,225,109,87,242,72,140,69,70,0,43,184,13,235,192,235, +140,27,138,215,207,39,223,188,233,210,115,250,228,205,199,137,88,29,252,139,138,216,19,129,237,90,214,98,238,31,252,194, +39,115,38,142,73,179,208,99,128,248,62,207,125,87,203,236,129,253,246,233,65,149,87,251,106,217,59,125,38,223,99,156,114, +39,26,242,198,198,239,106,130,239,229,242,197,231,7,149,111,248,2,19,129,115,229,141,171,36,91,119,36,83,231,205,213,101, +250,121,53,211,79,201,7,247,227,211,54,249,7,228,183,18,199,179,18,218,232,170,162,78,215,90,220,111,212,110,114,33,159, +9,23,206,160,218,7,125,46,113,249,130,31,174,194,9,80,88,224,51,244,46,117,113,155,13,69,179,168,227,240,34,172,2,191, +233,246,20,213,254,198,231,90,112,164,157,150,91,69,174,184,215,207,246,117,116,236,154,131,250,106,185,234,178,205,180, +106,234,248,115,21,120,85,188,250,54,175,3,252,21,123,164,56,115,174,187,42,11,222,147,81,255,32,234,107,236,118,222,181, +182,175,49,120,207,97,151,75,4,159,58,236,42,16,226,242,224,127,250,221,149,72,84,131,127,46,114,97,191,185,56,227,219, +128,214,31,201,198,166,45,176,133,41,215,37,182,74,125,247,232,43,245,89,62,17,126,12,179,218,35,150,47,184,108,30,45,39, +167,131,71,143,243,164,160,246,11,98,96,193,173,245,180,220,40,176,121,244,56,37,10,59,126,57,147,58,158,155,78,117,246, +156,204,169,239,232,88,82,40,103,132,122,107,98,233,199,40,218,239,28,42,49,69,127,240,47,135,17,179,15,219,182,8,254, +250,176,141,128,191,41,248,164,207,12,254,89,221,221,121,28,95,90,165,214,135,207,58,206,179,124,237,29,94,19,126,27,198, +121,238,19,241,165,65,106,243,5,223,225,247,202,134,204,151,238,130,252,77,28,223,138,176,34,69,105,49,147,119,175,139, +199,225,194,56,124,78,95,1,223,202,194,158,74,220,232,246,115,236,47,102,159,125,133,220,158,69,158,115,100,47,144,243, +248,122,58,94,105,67,212,99,235,186,200,227,169,44,86,103,234,43,210,186,56,83,109,117,131,70,164,176,148,62,229,251,33, +180,237,244,116,83,142,119,61,120,30,103,141,211,202,227,221,8,94,93,97,189,228,184,176,23,92,56,99,38,206,60,135,10,107, +143,27,27,34,228,43,133,117,197,93,56,151,14,97,214,157,5,109,84,234,121,202,121,209,67,182,151,243,67,236,236,165,119, +208,119,188,254,162,226,204,120,60,172,131,119,231,67,228,113,119,186,225,145,119,33,211,122,168,6,214,116,34,110,187, +100,156,112,202,248,224,164,52,206,151,82,242,23,5,159,43,242,248,139,26,61,165,158,81,10,62,93,228,9,190,205,123,99,18, +30,193,223,101,85,235,239,84,84,222,118,80,92,216,124,163,56,40,56,199,51,100,94,221,182,90,125,231,85,229,132,205,157, +124,54,185,113,122,179,205,113,118,27,225,107,213,124,12,94,7,216,232,50,172,67,167,3,209,245,90,196,162,192,103,233,54, +60,47,114,52,208,84,185,235,33,231,113,212,56,56,234,70,228,185,63,181,254,70,212,179,134,58,151,159,38,218,230,209,33, +147,79,231,203,200,239,44,38,61,2,139,87,143,243,136,74,151,90,189,203,100,92,199,234,137,42,105,45,33,243,82,73,29,108, +177,24,86,183,211,134,111,13,169,24,218,105,186,116,84,85,209,148,163,168,155,130,79,22,57,130,79,20,57,252,206,70,135, +58,99,199,97,247,9,29,63,119,203,189,132,8,122,97,115,122,23,114,132,66,25,53,4,237,131,141,62,204,54,194,76,171,165,109, +220,228,182,249,60,61,75,190,163,89,147,141,213,97,129,222,133,69,190,50,223,244,14,36,57,62,43,124,141,58,97,108,121,86, +221,174,169,60,179,176,162,175,30,213,103,150,60,97,54,52,206,34,173,221,213,177,255,213,163,178,45,172,217,32,61,87,157, +56,182,44,171,19,7,209,187,44,248,227,78,225,210,183,22,117,99,9,95,195,107,115,53,106,253,14,228,221,182,223,209,104, +171,185,158,37,227,196,230,236,251,171,71,86,79,189,251,241,61,248,71,171,179,241,118,253,94,154,23,246,200,236,195,144, +117,63,93,173,238,156,252,190,204,103,76,108,216,75,253,94,174,47,144,251,29,247,154,213,234,183,12,136,59,6,175,141,75, +174,17,191,153,230,44,208,164,105,134,58,209,249,61,159,19,22,236,180,113,210,88,193,183,112,95,177,26,141,112,114,58, +233,86,182,207,193,187,103,131,35,156,44,3,111,134,204,100,125,211,235,28,179,225,47,67,180,203,25,95,138,19,49,234,65, +150,131,21,207,107,39,91,9,191,37,230,5,31,231,59,234,126,10,254,77,189,151,116,99,166,252,93,231,169,210,6,21,217,24, +229,8,169,123,126,38,38,53,34,38,169,59,167,178,210,180,144,178,71,216,59,83,238,126,126,127,164,252,194,162,138,144,250, +189,5,207,209,45,119,6,71,55,181,147,194,215,170,168,114,155,228,227,206,114,173,138,40,183,201,181,134,127,26,106,213, +12,185,106,134,174,191,17,245,124,226,222,33,189,25,153,172,119,43,123,138,204,94,236,236,46,163,236,46,98,207,135,44, +172,11,223,235,159,114,195,112,196,3,147,216,21,42,123,240,59,131,247,40,175,47,18,149,14,220,154,92,165,242,214,116,25, +113,188,230,179,100,25,172,178,86,223,181,207,208,241,226,76,157,255,26,52,116,97,243,166,161,236,59,154,177,80,254,59, +154,179,196,44,58,219,168,162,179,204,106,249,246,73,221,48,63,26,82,239,203,125,98,17,98,238,52,228,31,87,200,76,138,41, +124,216,236,152,247,246,81,231,250,26,228,230,27,250,170,105,3,218,118,204,123,245,232,198,190,42,218,104,86,161,124,228, +232,134,190,89,224,227,196,157,247,252,81,95,73,240,183,153,247,112,68,159,12,169,239,10,234,112,55,216,208,59,139,158, +246,237,3,173,166,82,115,31,45,104,47,149,229,187,107,39,2,31,231,24,235,189,140,87,223,216,216,139,243,30,59,197,247, +198,55,106,167,137,82,113,33,5,95,131,214,191,169,219,48,209,23,245,58,251,96,179,26,192,143,185,100,222,5,29,10,169,124, +71,205,183,88,199,82,131,190,18,210,239,161,68,238,13,169,73,37,66,191,19,69,206,244,238,209,42,163,17,247,131,173,194, +79,139,4,178,106,81,131,53,235,64,134,30,3,199,47,249,193,35,153,156,191,68,234,16,250,123,12,33,179,43,83,247,245,88,72, +125,127,82,73,234,62,108,200,222,44,249,93,109,21,242,180,106,177,21,245,139,136,51,247,6,244,49,138,182,60,19,191,228,7, +95,205,220,191,139,116,31,213,217,62,170,228,111,79,229,221,95,219,130,231,250,81,159,250,254,231,82,208,43,179,191,110, +85,255,14,234,231,140,60,255,182,233,22,240,238,58,134,63,168,249,247,31,211,254,209,99,158,159,242,169,62,51,191,41,226, +119,135,207,250,212,111,122,94,247,169,239,73,254,234,83,191,91,56,226,83,191,105,98,189,219,88,184,84,253,94,5,14,77, +222,210,169,122,171,142,121,150,223,149,104,58,67,83,254,237,140,33,105,187,236,127,26,245,200,88,199,117,85,121,115,17, +164,114,25,67,63,153,154,246,80,238,119,81,153,239,118,12,73,219,228,243,28,205,239,207,202,121,116,91,245,11,15,213,215, +233,217,246,66,219,65,97,134,140,89,220,46,99,159,204,239,171,20,207,161,121,14,201,83,101,103,86,87,129,166,94,77,125, +90,198,167,245,10,101,62,202,124,167,101,72,58,139,50,223,177,177,44,127,199,59,71,247,203,223,171,206,209,123,176,1,255, +89,154,122,117,92,88,164,219,44,210,119,19,150,235,214,123,168,71,215,157,174,199,111,101,203,114,214,65,50,131,131,184, +203,204,165,250,182,142,190,206,182,129,5,189,205,3,203,6,58,155,231,247,117,116,52,247,158,182,160,189,121,97,255,64, +199,252,129,254,249,253,167,181,193,180,200,211,186,71,198,98,241,88,186,135,28,221,138,26,61,93,100,245,116,205,221,196, +159,40,123,251,198,38,163,233,68,34,189,99,77,36,30,217,30,77,210,226,99,57,129,104,50,153,72,46,14,140,36,38,199,70,3, +241,68,58,176,61,154,14,100,165,2,161,129,64,106,36,18,143,163,237,233,255,92,219,209,232,182,200,228,88,190,142,200,104, +100,34,13,5,149,203,38,199,199,247,102,249,43,34,233,116,127,100,108,108,107,100,4,23,155,65,50,6,67,100,14,134,66,84,51, +184,46,48,176,103,36,58,145,142,37,226,129,221,59,98,99,209,192,200,88,34,21,139,111,15,76,36,146,105,106,24,92,247,126, +245,227,177,209,24,134,176,43,54,18,37,177,138,172,85,27,251,7,168,100,213,228,72,116,13,106,6,227,19,147,233,245,172, +194,151,97,173,155,76,103,120,158,12,79,62,149,101,158,134,38,39,184,215,150,157,145,93,17,18,33,50,66,131,100,134,6,229, +7,122,192,7,50,10,12,219,12,225,195,10,133,54,135,168,62,20,137,143,38,19,177,209,214,173,153,217,182,102,231,221,171, +204,209,69,179,63,72,106,153,156,67,23,213,126,144,16,155,176,139,230,158,76,36,99,229,46,106,61,169,232,142,72,50,50, +130,225,197,82,233,216,72,23,157,122,178,6,203,162,169,145,100,108,34,157,72,158,120,32,99,209,156,124,40,58,164,124,233, +196,115,135,40,215,231,70,251,62,250,88,104,121,108,12,131,172,239,155,140,141,141,178,190,19,153,105,138,232,7,138,108, +136,166,224,178,39,158,173,22,25,138,166,211,112,176,84,174,203,15,152,66,70,184,139,102,102,133,70,18,241,116,52,158, +110,237,103,186,7,157,213,100,171,198,163,163,177,72,43,187,110,43,59,92,102,233,155,62,88,96,48,190,45,81,207,174,202, +133,252,225,188,175,116,23,53,124,176,208,80,58,146,158,196,168,235,222,79,44,187,129,242,93,233,24,25,29,29,234,149,202, +220,106,158,118,178,6,235,226,170,201,186,137,104,60,58,26,130,7,70,165,175,4,78,210,240,3,230,158,219,221,249,235,127, +140,208,134,232,72,52,182,139,245,148,102,69,18,169,214,190,201,248,232,24,150,161,44,159,185,50,194,76,136,150,231,115, +215,71,146,35,209,177,141,147,177,209,46,242,101,43,38,211,177,177,214,80,98,251,113,188,245,145,88,50,175,175,44,175, +139,54,30,207,236,62,137,155,156,52,62,224,32,104,11,141,36,198,91,147,137,177,88,235,78,68,181,214,99,66,91,253,177,145, +189,139,218,79,210,226,184,136,218,69,243,254,201,38,249,107,210,244,79,182,81,210,161,147,72,231,172,146,245,193,247,61, +113,186,104,217,191,172,45,199,97,23,13,71,82,231,158,220,80,199,105,57,249,164,51,19,94,31,73,239,224,48,241,129,210, +188,89,71,35,99,187,98,231,182,34,180,38,176,129,113,40,182,14,196,245,129,216,63,22,73,97,67,251,79,32,51,200,145,88, +215,215,158,160,126,77,116,124,171,22,136,66,164,250,4,34,67,177,237,113,68,140,36,118,73,229,9,170,195,59,146,137,221, +104,58,61,196,103,103,107,44,209,154,119,112,119,81,137,98,143,69,226,219,91,245,56,74,243,88,131,136,147,210,94,190,60, +230,186,173,59,163,35,233,169,188,161,116,18,51,205,118,35,121,178,235,200,86,222,191,85,121,236,100,116,91,235,153,209, +200,185,27,162,219,162,201,104,28,73,66,245,7,213,242,230,151,213,114,55,246,38,147,145,189,28,150,50,61,77,229,242,121, +117,2,118,247,241,35,237,201,142,63,39,154,154,202,91,25,73,97,51,78,100,12,146,207,59,94,16,199,205,113,130,224,77,29, +253,32,14,193,136,60,166,167,229,113,229,116,188,199,48,186,168,227,24,78,247,73,207,206,158,169,122,101,247,37,121,140, +112,108,156,215,114,250,177,44,181,139,74,142,219,38,212,123,28,235,196,249,102,222,65,16,72,237,197,153,49,30,72,69,147, +50,1,244,29,191,97,201,147,191,187,168,33,255,180,110,233,239,13,133,250,122,251,87,111,9,159,181,126,96,203,154,222,112, +255,202,45,161,117,67,97,18,155,200,216,132,132,111,19,82,84,107,211,224,230,65,114,108,90,133,20,112,21,216,72,252,54, +33,35,180,54,113,74,104,111,146,92,112,228,7,75,135,84,37,202,54,127,174,82,4,105,228,166,205,36,144,57,66,153,129,148, +209,24,238,163,186,225,147,103,49,205,195,255,82,86,80,255,79,136,99,219,13,159,96,139,77,97,102,246,88,97,100,100,36, +154,74,45,31,139,108,79,145,27,153,226,100,100,76,166,203,206,76,150,111,70,70,71,249,105,52,9,57,242,232,222,7,227,163, +209,61,104,173,158,100,11,119,100,98,66,39,67,228,136,164,148,39,110,61,38,75,166,202,44,39,52,32,195,158,90,219,141,27, +7,151,145,111,235,113,153,101,158,134,140,35,149,229,56,217,105,167,242,228,182,232,235,66,193,214,116,175,30,181,107, +107,90,201,65,76,151,82,124,22,195,4,228,216,154,230,115,132,236,173,156,8,146,103,68,31,40,225,189,19,81,114,96,20,200, +4,168,120,100,74,30,77,246,200,88,52,146,100,146,72,69,201,137,92,48,14,27,83,161,46,72,133,46,206,16,35,177,120,74,178, +101,105,117,116,175,20,150,54,242,232,66,56,177,17,58,108,236,130,120,154,196,40,185,71,179,41,56,57,244,92,92,138,194, +70,153,210,40,21,101,74,74,65,225,104,214,1,82,153,186,140,201,220,234,81,230,41,5,163,177,36,134,136,136,13,118,44,149, +25,186,35,122,30,150,62,69,5,114,83,246,39,70,97,192,104,38,182,83,203,182,8,110,101,163,129,116,34,48,146,140,70,210, +209,192,214,201,49,125,29,84,186,3,219,146,137,241,64,198,77,92,219,98,241,200,88,236,252,40,213,162,52,154,91,168,229, +137,100,222,197,73,9,215,176,72,102,67,159,72,192,222,22,75,194,153,60,219,96,162,209,204,130,187,185,67,229,198,100,109, +103,131,23,240,167,50,134,137,72,66,110,124,100,84,84,114,89,57,235,113,151,232,89,185,186,227,195,214,116,174,156,152, +24,139,141,200,51,48,227,224,165,96,31,55,206,138,124,102,126,6,46,181,28,127,109,34,23,216,242,164,164,18,148,150,169, +155,118,102,167,20,72,150,92,254,226,108,81,45,175,59,251,156,34,39,202,210,223,230,162,176,114,114,156,35,56,246,46,142, +74,101,156,19,26,20,162,240,37,73,70,165,6,214,75,85,178,160,147,185,181,145,113,94,45,78,79,212,102,159,137,90,126,58, +206,84,41,242,31,95,37,245,100,234,107,142,175,87,89,99,70,128,117,51,247,216,145,162,106,134,174,90,150,117,97,140,74, +143,154,199,72,213,40,228,114,208,227,134,93,136,234,76,5,21,101,30,38,57,23,162,114,253,200,103,199,148,70,46,93,145,82, +45,146,137,137,104,50,29,195,104,166,225,113,67,116,60,145,142,102,2,10,24,67,242,152,210,145,76,14,76,6,143,162,29,242, +114,161,175,35,228,220,17,73,173,101,223,113,161,176,67,238,48,107,71,2,126,93,192,159,202,111,69,140,204,216,232,30,178, +99,114,24,86,140,151,197,142,201,69,47,136,101,223,118,20,198,82,89,75,241,67,191,218,196,81,88,37,150,26,24,159,72,239, +229,130,92,2,174,206,189,38,113,197,116,214,64,46,78,62,87,202,238,119,102,167,237,217,153,255,198,196,60,23,225,202,129, +15,206,71,220,99,9,68,70,37,230,28,215,155,195,226,211,135,220,227,217,229,161,146,241,227,118,80,241,248,148,213,163, +194,241,60,207,49,198,199,201,28,79,109,199,71,122,146,172,56,47,146,205,159,136,33,241,232,110,222,62,48,83,156,205,102, +38,182,238,36,71,98,219,182,20,134,227,75,196,251,34,233,145,29,185,140,37,69,229,216,158,83,194,52,158,226,219,97,148, +178,99,43,120,135,208,244,99,185,103,38,97,29,169,69,153,19,219,93,246,175,212,144,55,17,207,189,28,145,26,74,242,57,170, +117,81,66,95,122,225,192,232,185,56,49,229,14,204,125,230,63,47,139,142,69,246,130,61,45,195,102,215,218,149,47,167,226, +71,102,34,206,68,124,249,216,100,106,7,121,18,241,53,233,201,12,27,35,227,241,40,191,220,144,74,197,168,130,57,99,49,142, +2,114,92,253,137,241,9,196,107,200,162,165,76,63,100,60,207,60,41,11,194,184,200,157,226,210,94,218,153,83,203,248,132, +192,93,26,178,165,216,4,241,99,194,27,185,153,169,203,69,92,206,249,154,159,31,167,220,41,207,140,165,119,228,246,88,77, +166,62,183,121,167,10,204,204,8,28,95,85,204,85,121,175,250,10,248,89,109,88,87,34,147,26,22,100,74,8,120,24,49,159,131, +137,92,19,59,177,155,67,112,233,4,124,242,216,89,85,158,128,57,148,142,78,132,119,39,168,124,74,93,46,50,145,53,193,25, +168,147,47,113,131,216,194,5,178,160,162,201,132,206,223,84,73,70,162,130,76,41,165,152,50,125,45,202,148,84,52,144,21, +50,148,20,103,74,225,196,114,132,6,50,121,103,207,72,70,183,243,75,149,228,212,55,51,228,72,74,47,34,183,162,42,98,168, +178,202,212,102,38,113,216,71,83,233,156,159,175,79,198,18,240,147,189,220,86,186,130,51,169,55,21,24,233,93,145,49,178, +146,236,87,102,114,50,78,37,169,108,254,170,95,158,81,105,42,47,239,206,48,157,153,55,205,174,212,200,142,232,40,18,6, +114,164,162,72,56,70,201,74,177,159,85,242,167,122,197,187,35,50,26,24,92,23,200,101,28,46,174,99,235,210,52,236,247,254, +252,164,172,16,12,246,218,53,28,66,139,249,65,231,144,147,177,81,84,238,224,235,4,246,13,38,106,165,56,5,177,83,242,161, +64,18,110,72,69,170,152,78,76,200,71,71,74,157,210,86,10,28,244,156,225,23,192,105,50,139,155,222,17,131,49,248,179,190, +13,21,184,234,160,209,248,4,57,211,9,121,223,163,233,147,241,19,185,210,204,99,216,121,14,83,49,25,127,159,21,180,97,241, +73,156,24,146,172,219,70,93,226,102,225,44,54,222,160,174,61,198,167,47,236,106,166,144,184,28,12,90,46,201,126,147,174, +17,22,255,191,111,197,116,171,16,100,89,223,52,22,143,56,139,143,154,116,191,81,180,217,38,186,92,136,175,179,252,39,133, +241,121,113,191,225,44,62,55,100,210,237,194,106,62,104,211,146,61,33,7,181,92,123,62,196,118,75,117,7,164,186,150,61,1, +58,67,252,192,112,54,65,244,83,194,108,49,252,187,141,109,213,33,83,92,37,10,90,174,104,217,108,26,223,54,10,175,219,108, +154,223,49,138,87,111,94,242,200,224,122,219,176,77,186,84,72,37,215,210,189,194,122,87,92,38,190,102,60,143,199,238,102, +252,235,166,223,10,114,86,135,182,172,222,219,220,108,236,170,246,155,244,13,209,66,15,129,89,220,221,77,143,49,165,183, +229,231,27,194,250,187,184,196,184,69,252,15,6,219,124,43,253,93,152,234,25,117,79,176,196,35,155,151,208,207,50,133,3, +134,169,186,82,29,209,125,198,9,186,249,140,161,186,57,200,148,14,25,89,133,107,182,24,23,84,215,40,161,27,101,229,205, +242,243,77,195,160,119,81,223,220,221,76,151,154,198,3,226,122,238,253,98,211,228,210,19,232,139,46,201,43,191,107,160, +252,132,177,15,229,37,171,175,163,143,115,213,109,170,234,178,188,242,229,92,126,79,149,15,112,249,91,134,44,239,231,14, +100,233,162,108,233,179,166,69,119,137,219,197,183,208,239,102,158,215,245,38,198,181,164,27,11,242,128,113,122,104,243, +112,207,218,115,122,154,109,50,246,118,57,136,30,144,149,161,152,41,254,191,40,221,251,136,92,196,230,115,108,178,197, +172,154,69,244,35,174,165,39,229,231,79,165,228,254,61,254,42,250,157,201,158,85,109,220,96,117,25,47,94,208,212,252,104, +200,40,222,13,155,237,217,179,103,111,12,238,35,250,149,190,197,61,182,160,95,216,114,105,133,207,107,25,111,139,154,222, +253,249,93,61,198,61,217,6,189,164,133,102,120,77,58,36,218,32,115,147,209,112,136,43,233,57,39,247,123,192,52,126,47, +250,177,40,47,8,33,156,54,153,2,133,71,44,147,53,10,195,22,14,18,133,54,57,68,141,109,54,75,141,119,88,226,110,182,106, +172,102,216,50,191,98,44,216,140,182,47,162,173,101,26,95,54,26,155,119,25,23,236,134,122,248,94,191,131,28,134,195,196, +136,143,138,1,122,205,41,238,192,64,150,8,223,52,139,88,249,115,1,155,102,213,156,66,15,88,230,139,226,57,241,10,87,118, +155,5,95,55,68,200,52,161,169,117,191,209,208,100,132,171,109,211,46,88,224,48,29,5,59,45,231,87,208,174,101,181,233,184, +93,148,180,192,69,222,16,179,91,118,154,198,205,198,204,102,12,149,70,108,3,91,231,182,118,116,111,218,14,219,105,156, +135,133,64,75,135,195,185,211,116,29,17,211,165,148,48,29,100,120,170,33,4,17,219,85,67,239,194,254,213,195,75,134,197, +140,105,166,128,141,254,203,22,139,187,107,248,201,248,147,104,132,121,109,163,133,141,140,29,90,211,27,218,115,254,229, +54,13,119,211,95,45,54,108,245,146,157,177,133,198,158,234,253,237,157,177,150,26,250,155,45,94,100,239,48,137,77,17,162, +27,45,233,191,242,243,18,199,212,186,59,156,226,16,236,184,215,116,98,202,75,238,149,139,231,55,141,55,133,184,237,54, +211,250,178,225,195,60,239,54,4,166,107,222,105,136,157,171,77,251,46,99,94,76,184,109,187,197,97,55,240,82,96,150,150, +109,219,14,99,164,218,118,46,112,8,88,219,226,201,26,23,116,161,194,97,148,175,98,41,246,54,167,113,183,241,186,112,54, +249,77,113,135,33,16,81,208,215,109,188,94,190,157,205,7,62,201,203,37,4,175,150,177,187,186,214,216,221,180,200,54,12, +239,42,68,160,79,58,48,42,103,241,5,85,244,176,67,60,37,247,146,233,66,91,12,237,144,16,126,211,249,128,176,107,204,130, +151,196,233,207,239,245,63,98,218,28,168,86,155,214,213,98,222,109,194,101,59,155,49,150,234,221,60,200,123,204,66,248, +221,189,162,196,107,23,250,141,209,106,12,212,190,213,114,191,195,235,185,179,165,251,244,165,182,123,161,154,12,175,137, +93,176,136,231,225,112,57,10,28,133,198,100,151,93,200,242,244,174,30,3,92,22,189,63,122,224,40,111,88,251,90,163,230,22, +219,244,35,24,162,103,140,237,17,83,168,97,16,134,177,223,178,101,31,33,135,217,210,221,39,29,195,14,181,74,227,89,116, +141,154,221,146,3,22,105,199,130,215,151,172,49,133,156,149,117,159,113,250,18,63,111,28,118,131,3,162,100,154,191,14, +158,208,221,51,140,230,176,188,191,10,6,122,208,161,98,195,31,45,131,157,24,165,63,89,114,153,233,74,27,195,112,54,169, +97,24,51,118,24,227,213,75,98,70,113,19,166,115,171,240,122,101,39,45,215,183,204,164,67,82,176,56,70,111,217,210,33,54, +211,87,212,158,53,130,59,140,225,234,37,7,100,44,178,209,241,217,182,1,47,128,29,121,114,176,64,55,142,139,213,150,245, +78,110,236,231,247,217,38,124,28,134,132,15,192,243,97,181,26,83,112,36,125,194,48,239,20,95,21,87,168,224,223,66,207,27, +226,98,195,89,221,49,243,190,102,147,190,100,52,210,47,57,200,98,115,154,244,59,209,246,13,155,186,17,195,110,118,97,103, +52,211,28,118,223,207,186,196,151,224,239,8,118,251,133,105,120,154,224,42,231,46,89,189,7,222,117,169,172,41,222,223,96, +156,95,173,56,70,80,252,220,168,104,52,230,26,151,10,171,224,61,81,94,104,156,10,78,149,85,254,225,114,187,124,168,220, +165,30,237,114,81,254,17,48,154,202,11,164,104,125,129,3,178,21,185,102,51,140,38,150,19,21,243,114,188,114,165,188,65, +113,12,112,138,115,197,29,57,185,109,198,41,220,214,168,168,175,152,157,233,110,88,246,94,159,235,159,25,103,148,47,207, +48,156,229,103,130,209,3,180,65,202,157,97,178,212,202,242,141,248,92,150,97,186,48,244,161,114,51,43,171,21,56,202,141, +60,150,28,139,27,99,137,169,177,56,42,26,42,230,84,212,86,4,42,106,42,234,68,185,37,76,81,96,86,26,248,39,140,142,125, +251,172,91,230,204,23,207,204,17,226,239,192,161,70,33,126,5,28,12,10,113,23,112,104,174,16,111,2,247,240,143,174,237, +181,23,185,72,28,3,108,98,151,67,255,230,70,208,146,139,246,89,135,79,53,47,54,168,71,28,104,18,214,43,77,134,184,178,89, +136,155,128,123,128,103,129,55,129,187,91,16,241,29,133,234,215,242,104,243,74,203,74,113,85,171,176,222,108,21,226,234, +54,244,11,188,2,236,107,39,67,184,139,13,113,85,96,8,98,87,183,135,197,61,237,66,252,0,248,21,240,58,112,203,60,33,238,7, +126,12,60,11,188,9,28,232,32,75,216,94,76,80,112,211,115,208,244,134,142,45,226,209,14,33,158,154,47,196,51,11,160,29, +184,122,33,57,157,165,101,74,76,255,55,10,217,103,23,26,198,193,78,97,220,180,200,48,14,44,6,186,76,227,234,238,66,227, +192,146,168,245,66,143,41,238,233,53,196,221,125,134,120,182,15,86,3,61,216,143,33,1,119,15,96,40,203,133,56,2,252,106, +16,163,15,9,113,120,13,250,2,14,172,53,196,161,181,224,175,67,27,224,224,122,88,192,152,37,248,223,199,208,231,85,27,46, +20,247,108,16,226,170,33,254,13,158,223,45,220,23,137,3,251,172,23,134,4,42,247,133,69,193,65,224,210,141,148,251,251,69, +249,191,225,201,252,109,62,254,109,74,230,239,243,241,239,82,50,127,163,143,127,151,18,32,245,119,250,248,183,57,153,191, +213,231,160,220,223,235,51,189,234,183,52,242,119,83,1,245,183,154,234,75,33,19,80,50,252,255,180,11,175,250,253,61,255, +127,232,70,64,245,203,127,223,207,212,242,252,255,75,91,1,245,187,27,254,127,170,237,128,26,31,255,127,240,164,245,200, +31,228,121,21,159,255,174,224,255,1,51,160,178,16,144,80,0,0}; + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$MidiDeviceManager;") \ + STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$BluetoothManager;") + +DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/roli/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode)) +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \ METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \ - METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ - METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ + METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") \ + METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") \ METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \ METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;") - DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager") + +DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/roli/juce/JuceMidiSupport$MidiDeviceManager", 23) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (start, "start", "()V" )\ METHOD (stop, "stop", "()V") \ METHOD (close, "close", "()V") \ METHOD (sendMidi, "sendMidi", "([BII)V") - DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort") -#undef JNI_CLASS_MEMBERS +DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/roli/juce/JuceMidiSupport$JuceMidiPort", 23) +#undef JNI_CLASS_MEMBERS //============================================================================== class AndroidMidiInput @@ -51,10 +373,10 @@ public: : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), - javaMidiDevice (getEnv()->CallObjectMethod (deviceManager, - MidiDeviceManager.openMidiInputPortWithJuceIndex, - (jint) portIdx, - (jlong) this)) + javaMidiDevice (LocalRef(getEnv()->CallObjectMethod (deviceManager, + MidiDeviceManager.openMidiInputPortWithJuceIndex, + (jint) portIdx, + (jlong) this))) { } @@ -86,10 +408,12 @@ public: callback = nullptr; } - void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) + void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) { + auto* env = getEnv(); + jassert (byteArray != nullptr); - jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr); + jbyte* data = env->GetByteArrayElements (byteArray, nullptr); HeapBlock buffer (static_cast (len)); std::memcpy (buffer.get(), data + offset, static_cast (len)); @@ -98,7 +422,15 @@ public: len, static_cast (timestamp) * 1.0e-9, juceMidiInput, *callback); - getEnv()->ReleaseByteArrayElements (byteArray, data, 0); + env->ReleaseByteArrayElements (byteArray, data, 0); + } + + static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, + jint offset, jint len, jlong timestamp) + { + auto* myself = reinterpret_cast (host); + + myself->handleMidi (byteArray, offset, len, timestamp); } private: @@ -112,7 +444,7 @@ private: class AndroidMidiOutput { public: - AndroidMidiOutput (jobject midiDevice) + AndroidMidiOutput (const LocalRef& midiDevice) : javaMidiDevice (midiDevice) { } @@ -138,24 +470,19 @@ private: GlobalRef javaMidiDevice; }; -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive, - void, (JNIEnv* env, jobject, jlong host, jbyteArray byteArray, - jint offset, jint count, jlong timestamp)) -{ - // Java may create a Midi thread which JUCE doesn't know about and this callback may be - // received on this thread. Java will have already created a JNI Env for this new thread, - // which we need to tell JUCE about - setEnv (env); +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + CALLBACK (AndroidMidiInput::handleReceive, "handleReceive", "(J[BIIJ)V" ) - reinterpret_cast (host)->receive (byteArray, offset, count, timestamp); -} +DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/roli/juce/JuceMidiSupport$JuceMidiInputPort", 23) +#undef JNI_CLASS_MEMBERS //============================================================================== class AndroidMidiDeviceManager { public: AndroidMidiDeviceManager() - : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) + : deviceManager (LocalRef(getEnv()->CallStaticObjectMethod (JuceMidiSupport, JuceMidiSupport.getAndroidMidiDeviceManager, getAppContext().get()))) { } @@ -187,7 +514,7 @@ public: { jobjectArray jDevices = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices - : MidiDeviceManager.getJuceAndroidMidiOutputDevices); + : MidiDeviceManager.getJuceAndroidMidiOutputDevices); // Create a local reference as converting this // to a JUCE string will call into JNI @@ -215,7 +542,7 @@ public: { if (jobject dm = deviceManager.get()) if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx)) - return new AndroidMidiOutput (javaMidiPort); + return new AndroidMidiOutput (LocalRef(javaMidiPort)); return nullptr; } @@ -227,18 +554,23 @@ private: //============================================================================== StringArray MidiOutput::getDevices() { - AndroidMidiDeviceManager manager; - return manager.getDevices (false); + if (getAndroidSDKVersion() >= 23) + { + AndroidMidiDeviceManager manager; + return manager.getDevices (false); + } + + return {}; } int MidiOutput::getDefaultDeviceIndex() { - return 0; + return (getAndroidSDKVersion() >= 23 ? 0 : -1); } MidiOutput* MidiOutput::openDevice (int index) { - if (index < 0) + if (index < 0 || getAndroidSDKVersion() < 23) return nullptr; AndroidMidiDeviceManager manager; @@ -295,18 +627,23 @@ MidiInput::MidiInput (const String& nm) : name (nm) StringArray MidiInput::getDevices() { - AndroidMidiDeviceManager manager; - return manager.getDevices (true); + if (getAndroidSDKVersion() >= 23) + { + AndroidMidiDeviceManager manager; + return manager.getDevices (true); + } + + return {}; } int MidiInput::getDefaultDeviceIndex() { - return 0; + return (getAndroidSDKVersion() >= 23 ? 0 : -1); } MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback) { - if (index < 0) + if (getAndroidSDKVersion() < 23 || index < 0) return nullptr; AndroidMidiDeviceManager manager; diff --git a/modules/juce_audio_devices/native/juce_android_Oboe.cpp b/modules/juce_audio_devices/native/juce_android_Oboe.cpp index 0798687c10..5e18780f9f 100644 --- a/modules/juce_audio_devices/native/juce_android_Oboe.cpp +++ b/modules/juce_audio_devices/native/juce_android_Oboe.cpp @@ -760,8 +760,6 @@ private: oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override { - attachAndroidJNI(); - if (audioCallbackGuard.compareAndSetBool (1, 0)) { if (stream == nullptr) @@ -905,8 +903,6 @@ private: void onErrorBeforeClose (oboe::AudioStream* stream, oboe::Result error) override { - attachAndroidJNI(); - // only output stream should be the master stream receiving callbacks jassert (stream->getDirection() == oboe::Direction::Output); @@ -916,8 +912,6 @@ private: void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override { - attachAndroidJNI(); - // only output stream should be the master stream receiving callbacks jassert (stream->getDirection() == oboe::Direction::Output); @@ -987,23 +981,6 @@ private: bool running = false; //============================================================================== - static String audioManagerGetProperty (const String& property) - { - const LocalRef jProperty (javaString (property)); - const LocalRef text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty, - jProperty.get())); - if (text.get() != 0) - return juceString (text); - - return {}; - } - - static bool androidHasSystemFeature (const String& property) - { - const LocalRef jProperty (javaString (property)); - return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get()); - } - static double getNativeSampleRate() { return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); @@ -1023,18 +1000,9 @@ private: static int getDefaultFramesPerBurst() { // NB: this function only works for inbuilt speakers and headphones - auto* env = getEnv(); - - auto audioManager = LocalRef (env->CallObjectMethod (android.activity, - JuceAppActivity.getSystemService, - javaString ("audio").get())); - - auto propertyJavaString = javaString ("android.media.property.OUTPUT_FRAMES_PER_BUFFER"); + auto framesPerBurstString = javaString (audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER")); - auto framesPerBurstString = LocalRef ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty, - propertyJavaString.get())); - - return framesPerBurstString != 0 ? env->CallStaticIntMethod (JavaInteger, JavaInteger.parseInt, framesPerBurstString.get(), 10) : 192; + return framesPerBurstString != 0 ? getEnv()->CallStaticIntMethod (JavaInteger, JavaInteger.parseInt, framesPerBurstString.get(), 10) : 192; } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODevice) @@ -1051,7 +1019,7 @@ OboeAudioIODevice::OboeSessionBase* OboeAudioIODevice::OboeSessionBase::create ( { std::unique_ptr session; - auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT); + auto sdkVersion = getAndroidSDKVersion(); // SDK versions 21 and higher should natively support floating point... if (sdkVersion >= 21) @@ -1119,7 +1087,7 @@ public: forInput ? oboe::Direction::Input : oboe::Direction::Output, oboe::SharingMode::Shared, forInput ? 1 : 2, - getSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16, + getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16, (int) OboeAudioIODevice::getNativeSampleRate(), OboeAudioIODevice::getNativeBufferSize(), nullptr); @@ -1210,8 +1178,8 @@ public: if (audioManagerClass == 0) return; - auto audioManager = LocalRef (env->CallObjectMethod (android.activity, - JuceAppActivity.getSystemService, + auto audioManager = LocalRef (env->CallObjectMethod (getAppContext().get(), + AndroidContext.getSystemService, javaString ("audio").get())); static jmethodID getDevicesMethod = env->GetMethodID (audioManagerClass, "getDevices", @@ -1253,16 +1221,10 @@ public: bool supportsDevicesInfo() const { - static auto result = getSDKVersion() >= 23; + static auto result = getAndroidSDKVersion() >= 23; return result; } - int getSDKVersion() const - { - static auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT); - return sdkVersion; - } - void addDevice (const LocalRef& device, JNIEnv* env) { auto deviceClass = LocalRef ((jclass) env->FindClass ("android/media/AudioDeviceInfo")); diff --git a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp index d24b5b0285..158dfa2f26 100644 --- a/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ b/modules/juce_audio_devices/native/juce_android_OpenSL.cpp @@ -23,6 +23,10 @@ namespace juce { +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ +DECLARE_JNI_CLASS (AndroidAudioManager, "android/media/AudioManager") +#undef JNI_CLASS_MEMBERS + //============================================================================== #ifndef SL_ANDROID_DATAFORMAT_PCM_EX #define SL_ANDROID_DATAFORMAT_PCM_EX ((SLuint32) 0x00000004) @@ -323,7 +327,7 @@ public: if (runner == nullptr) return false; - const bool supportsJavaProxy = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24); + const bool supportsJavaProxy = (getAndroidSDKVersion() >= 24); if (supportsJavaProxy) { @@ -337,7 +341,7 @@ public: &audioRoutingJni); if (status == SL_RESULT_SUCCESS && audioRoutingJni != 0) - javaProxy = GlobalRef (audioRoutingJni); + javaProxy = GlobalRef (LocalRef(getEnv()->NewLocalRef (audioRoutingJni))); } } @@ -372,8 +376,6 @@ public: void finished (SLAndroidSimpleBufferQueueItf) { - attachAndroidJNI(); - --numBlocksOut; owner.doSomeWorkOnAudioThread(); } @@ -489,8 +491,7 @@ public: { if (Base::config != nullptr) { - const bool supportsUnprocessed = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 25); - + const bool supportsUnprocessed = (getAndroidSDKVersion() >= 25); const SLuint32 recordingPresetValue = (shouldEnable ? SL_ANDROID_RECORDING_PRESET_GENERIC : (supportsUnprocessed ? SL_ANDROID_RECORDING_PRESET_UNPROCESSED @@ -660,7 +661,7 @@ public: return; } - const bool supportsUnderrunCount = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24); + const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24); getUnderrunCount = supportsUnderrunCount ? getEnv()->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0; } } @@ -1048,9 +1049,7 @@ private: // "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required // for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one // is sufficient for lower latency." - - auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT); - return (sdkVersion >= 18 ? 1 : 2); + return (getAndroidSDKVersion() >= 18 ? 1 : 2); } // we will not use the low-latency path so we can use the absolute minimum number of buffers @@ -1079,23 +1078,6 @@ private: } //============================================================================== - static String audioManagerGetProperty (const String& property) - { - const LocalRef jProperty (javaString (property)); - const LocalRef text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty, - jProperty.get())); - if (text.get() != 0) - return juceString (text); - - return {}; - } - - static bool androidHasSystemFeature (const String& property) - { - const LocalRef jProperty (javaString (property)); - return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get()); - } - static double getNativeSampleRate() { return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); @@ -1147,7 +1129,7 @@ OpenSLAudioIODevice::OpenSLSession* OpenSLAudioIODevice::OpenSLSession::create ( int numBuffersToUse) { std::unique_ptr retval; - auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT); + auto sdkVersion = getAndroidSDKVersion(); // SDK versions 21 and higher should natively support floating point... if (sdkVersion >= 21) diff --git a/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp b/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp index 1911e83abc..664b4837e3 100644 --- a/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp +++ b/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp @@ -27,7 +27,13 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$BluetoothManager;") + +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/roli/juce/JuceMidiSupport", 23) +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getMidiBluetoothAddresses, "getMidiBluetoothAddresses", "()[Ljava/lang/String;") \ METHOD (pairBluetoothMidiDevice, "pairBluetoothMidiDevice", "(Ljava/lang/String;)Z") \ METHOD (unpairBluetoothMidiDevice, "unpairBluetoothMidiDevice", "(Ljava/lang/String;)V") \ @@ -35,7 +41,7 @@ namespace juce METHOD (getBluetoothDeviceStatus, "getBluetoothDeviceStatus", "(Ljava/lang/String;)I") \ METHOD (startStopScan, "startStopScan", "(Z)V") -DECLARE_JNI_CLASS (AndroidBluetoothManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/roli/juce/JuceMidiSupport$BluetoothManager", 23) #undef JNI_CLASS_MEMBERS //============================================================================== @@ -44,7 +50,7 @@ struct AndroidBluetoothMidiInterface static void startStopScan (bool startScanning) { JNIEnv* env = getEnv(); - LocalRef btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); + LocalRef btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get())); if (btManager.get() != nullptr) env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.startStopScan, (jboolean) (startScanning ? 1 : 0)); @@ -56,7 +62,7 @@ struct AndroidBluetoothMidiInterface JNIEnv* env = getEnv(); - LocalRef btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); + LocalRef btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get())); // if this is null then bluetooth is not enabled if (btManager.get() == nullptr) @@ -82,7 +88,7 @@ struct AndroidBluetoothMidiInterface { JNIEnv* env = getEnv(); - LocalRef btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); + LocalRef btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get())); if (btManager.get() == nullptr) return false; @@ -96,7 +102,7 @@ struct AndroidBluetoothMidiInterface { JNIEnv* env = getEnv(); - LocalRef btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); + LocalRef btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get())); if (btManager.get() != nullptr) env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.unpairBluetoothMidiDevice, @@ -108,7 +114,7 @@ struct AndroidBluetoothMidiInterface { JNIEnv* env = getEnv(); - LocalRef btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); + LocalRef btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get())); if (btManager.get() == nullptr) return address; @@ -136,7 +142,7 @@ struct AndroidBluetoothMidiInterface { JNIEnv* env = getEnv(); - LocalRef btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); + LocalRef btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get())); if (btManager.get() == nullptr) return unpaired; @@ -485,6 +491,10 @@ bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* Rectangle* btBounds) { std::unique_ptr exitCallback (exitCallbackPtr); + + if (getAndroidSDKVersion() < 23) + return false; + auto boundsToUse = (btBounds != nullptr ? *btBounds : Rectangle {}); if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi)) @@ -502,7 +512,12 @@ bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* bool BluetoothMidiDevicePairingDialogue::isAvailable() { - jobject btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager)); + if (getAndroidSDKVersion() < 23) + return false; + + auto* env = getEnv(); + + LocalRef btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get())); return btManager != nullptr; } diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp index 111060cfd8..a1e9c90b91 100644 --- a/modules/juce_core/juce_core.cpp +++ b/modules/juce_core/juce_core.cpp @@ -221,7 +221,9 @@ //============================================================================== #elif JUCE_ANDROID + #include "native/juce_linux_CommonFile.cpp" +#include "native/juce_android_JNIHelpers.cpp" #include "native/juce_android_Files.cpp" #include "native/juce_android_Misc.cpp" #include "native/juce_android_Network.cpp" diff --git a/modules/juce_core/native/java/AndroidCamera.java b/modules/juce_core/native/java/AndroidCamera.java deleted file mode 100644 index 936e46d851..0000000000 --- a/modules/juce_core/native/java/AndroidCamera.java +++ /dev/null @@ -1,169 +0,0 @@ -$$CameraApi21 - //============================================================================== - public class CameraDeviceStateCallback extends CameraDevice.StateCallback - { - private native void cameraDeviceStateClosed (long host, CameraDevice camera); - private native void cameraDeviceStateDisconnected (long host, CameraDevice camera); - private native void cameraDeviceStateError (long host, CameraDevice camera, int error); - private native void cameraDeviceStateOpened (long host, CameraDevice camera); - - CameraDeviceStateCallback (long hostToUse) - { - host = hostToUse; - } - - @Override - public void onClosed (CameraDevice camera) - { - cameraDeviceStateClosed (host, camera); - } - - @Override - public void onDisconnected (CameraDevice camera) - { - cameraDeviceStateDisconnected (host, camera); - } - - @Override - public void onError (CameraDevice camera, int error) - { - cameraDeviceStateError (host, camera, error); - } - - @Override - public void onOpened (CameraDevice camera) - { - cameraDeviceStateOpened (host, camera); - } - - private long host; - } - - //============================================================================== - public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback - { - private native void cameraCaptureSessionActive (long host, CameraCaptureSession session); - private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session); - private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session); - private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session); - private native void cameraCaptureSessionReady (long host, CameraCaptureSession session); - - CameraCaptureSessionStateCallback (long hostToUse) - { - host = hostToUse; - } - - @Override - public void onActive (CameraCaptureSession session) - { - cameraCaptureSessionActive (host, session); - } - - @Override - public void onClosed (CameraCaptureSession session) - { - cameraCaptureSessionClosed (host, session); - } - - @Override - public void onConfigureFailed (CameraCaptureSession session) - { - cameraCaptureSessionConfigureFailed (host, session); - } - - @Override - public void onConfigured (CameraCaptureSession session) - { - cameraCaptureSessionConfigured (host, session); - } - - @Override - public void onReady (CameraCaptureSession session) - { - cameraCaptureSessionReady (host, session); - } - - private long host; - } - - //============================================================================== - public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback - { - private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result); - private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure); - private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult); - private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber); - private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId); - private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber); - - CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview) - { - host = hostToUse; - preview = shouldBePreview; - } - - @Override - public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request, - TotalCaptureResult result) - { - cameraCaptureSessionCaptureCompleted (host, preview, session, request, result); - } - - @Override - public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) - { - cameraCaptureSessionCaptureFailed (host, preview, session, request, failure); - } - - @Override - public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request, - CaptureResult partialResult) - { - cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult); - } - - @Override - public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId) - { - cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId); - } - - @Override - public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber) - { - cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber); - } - - @Override - public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp, - long frameNumber) - { - cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber); - } - - private long host; - private boolean preview; - } - - //============================================================================== - public class JuceOrientationEventListener extends OrientationEventListener - { - private native void deviceOrientationChanged (long host, int orientation); - - public JuceOrientationEventListener (long hostToUse, Context context, int rate) - { - super (context, rate); - - host = hostToUse; - } - - @Override - public void onOrientationChanged (int orientation) - { - deviceOrientationChanged (host, orientation); - } - - private long host; - } - -CameraApi21$$ diff --git a/modules/juce_core/native/java/AndroidMidiFallback.java b/modules/juce_core/native/java/AndroidMidiFallback.java deleted file mode 100644 index 5e3d57ff4e..0000000000 --- a/modules/juce_core/native/java/AndroidMidiFallback.java +++ /dev/null @@ -1,85 +0,0 @@ - //============================================================================== - public class BluetoothManager - { - BluetoothManager() - { - } - - public String[] getMidiBluetoothAddresses() - { - String[] bluetoothAddresses = new String[0]; - return bluetoothAddresses; - } - - public String getHumanReadableStringForBluetoothAddress (String address) - { - return address; - } - - public int getBluetoothDeviceStatus (String address) - { - return 0; - } - - public void startStopScan (boolean shouldStart) - { - } - - public boolean pairBluetoothMidiDevice(String address) - { - return false; - } - - public void unpairBluetoothMidiDevice (String address) - { - } - } - - //============================================================================== - public class MidiDeviceManager - { - public MidiDeviceManager() - { - } - - public String[] getJuceAndroidMidiInputDevices() - { - return new String[0]; - } - - public String[] getJuceAndroidMidiOutputDevices() - { - return new String[0]; - } - - public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) - { - return null; - } - - public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) - { - return null; - } - - public String getInputPortNameForJuceIndex (int index) - { - return ""; - } - - public String getOutputPortNameForJuceIndex (int index) - { - return ""; - } - } - - - public MidiDeviceManager getAndroidMidiDeviceManager() - { - return null; - } - - public BluetoothManager getAndroidBluetoothManager() - { - return null; - } diff --git a/modules/juce_core/native/java/AndroidRuntimePermissions.java b/modules/juce_core/native/java/AndroidRuntimePermissions.java deleted file mode 100644 index 6e1b160a84..0000000000 --- a/modules/juce_core/native/java/AndroidRuntimePermissions.java +++ /dev/null @@ -1,12 +0,0 @@ - @Override - public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults) - { - boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED); - - if (! permissionsGranted) - Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID)); - - Long ptrToCallback = permissionCallbackPtrMap.get (permissionID); - permissionCallbackPtrMap.remove (permissionID); - androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback); - } diff --git a/modules/juce_core/native/java/AndroidVideo.java b/modules/juce_core/native/java/AndroidVideo.java deleted file mode 100644 index abda5e0c15..0000000000 --- a/modules/juce_core/native/java/AndroidVideo.java +++ /dev/null @@ -1,146 +0,0 @@ -$$VideoApi21 - //============================================================================== - public class MediaControllerCallback extends MediaController.Callback - { - private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info); - private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata); - private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state); - private native void mediaControllerSessionDestroyed (long host); - - MediaControllerCallback (long hostToUse) - { - host = hostToUse; - } - - @Override - public void onAudioInfoChanged (MediaController.PlaybackInfo info) - { - mediaControllerAudioInfoChanged (host, info); - } - - @Override - public void onMetadataChanged (MediaMetadata metadata) - { - mediaControllerMetadataChanged (host, metadata); - } - - @Override - public void onPlaybackStateChanged (PlaybackState state) - { - mediaControllerPlaybackStateChanged (host, state); - } - - @Override - public void onQueueChanged (List queue) {} - - @Override - public void onSessionDestroyed() - { - mediaControllerSessionDestroyed (host); - } - - private long host; - } - - //============================================================================== - public class MediaSessionCallback extends MediaSession.Callback - { - private native void mediaSessionPause (long host); - private native void mediaSessionPlay (long host); - private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras); - private native void mediaSessionSeekTo (long host, long pos); - private native void mediaSessionStop (long host); - - - MediaSessionCallback (long hostToUse) - { - host = hostToUse; - } - - @Override - public void onPause() - { - mediaSessionPause (host); - } - - @Override - public void onPlay() - { - mediaSessionPlay (host); - } - - @Override - public void onPlayFromMediaId (String mediaId, Bundle extras) - { - mediaSessionPlayFromMediaId (host, mediaId, extras); - } - - @Override - public void onSeekTo (long pos) - { - mediaSessionSeekTo (host, pos); - } - - @Override - public void onStop() - { - mediaSessionStop (host); - } - - @Override - public void onFastForward() {} - - @Override - public boolean onMediaButtonEvent (Intent mediaButtonIntent) - { - return true; - } - - @Override - public void onRewind() {} - - @Override - public void onSkipToNext() {} - - @Override - public void onSkipToPrevious() {} - - @Override - public void onSkipToQueueItem (long id) {} - - private long host; - } - - //============================================================================== - public class SystemVolumeObserver extends ContentObserver - { - private native void mediaSessionSystemVolumeChanged (long host); - - SystemVolumeObserver (Activity activityToUse, long hostToUse) - { - super (null); - - activity = activityToUse; - host = hostToUse; - } - - void setEnabled (boolean shouldBeEnabled) - { - if (shouldBeEnabled) - activity.getApplicationContext().getContentResolver().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this); - else - activity.getApplicationContext().getContentResolver().unregisterContentObserver (this); - } - - @Override - public void onChange (boolean selfChange, Uri uri) - { - if (uri.toString().startsWith ("content://settings/system/volume_music")) - mediaSessionSystemVolumeChanged (host); - } - - private Activity activity; - private long host; - } - -VideoApi21$$ diff --git a/modules/juce_core/native/java/AndroidWebView.java b/modules/juce_core/native/java/AndroidWebView.java deleted file mode 100644 index 74d851ff70..0000000000 --- a/modules/juce_core/native/java/AndroidWebView.java +++ /dev/null @@ -1,69 +0,0 @@ -$$WebViewNativeApi23 private native void webViewReceivedError (long host, WebView view, WebResourceRequest request, WebResourceError error);WebViewNativeApi23$$ -$$WebViewNativeApi21 private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse);WebViewNativeApi21$$ - -$$WebViewApi1_10 - @Override - public void onPageStarted (WebView view, String url, Bitmap favicon) - { - if (host != 0) - webViewPageLoadStarted (host, view, url); - } -WebViewApi1_10$$ - -$$WebViewApi11_20 - @Override - public WebResourceResponse shouldInterceptRequest (WebView view, String url) - { - synchronized (hostLock) - { - if (host != 0) - { - boolean shouldLoad = webViewPageLoadStarted (host, view, url); - - if (shouldLoad) - return null; - } - } - - return new WebResourceResponse ("text/html", null, null); - } -WebViewApi11_20$$ - -$$WebViewApi21 - @Override - public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) - { - synchronized (hostLock) - { - if (host != 0) - { - boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString()); - - if (shouldLoad) - return null; - } - } - - return new WebResourceResponse ("text/html", null, null); - } -WebViewApi21$$ - -$$WebViewApi23 - @Override - public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error) - { - if (host == 0) - return; - - webViewReceivedError (host, view, request, error); - } - - @Override - public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse) - { - if (host == 0) - return; - - webViewReceivedHttpError (host, view, request, errorResponse); - } -WebViewApi23$$ diff --git a/modules/juce_core/native/java/IInAppBillingService.java b/modules/juce_core/native/java/IInAppBillingService.java deleted file mode 100644 index 0bb31cb5d3..0000000000 --- a/modules/juce_core/native/java/IInAppBillingService.java +++ /dev/null @@ -1,971 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -package com.android.vending.billing; -/** - * InAppBillingService is the service that provides in-app billing version 3 and beyond. - * This service provides the following features: - * 1. Provides a new API to get details of in-app items published for the app including - * price, type, title and description. - * 2. The purchase flow is synchronous and purchase information is available immediately - * after it completes. - * 3. Purchase information of in-app purchases is maintained within the Google Play system - * till the purchase is consumed. - * 4. An API to consume a purchase of an inapp item. All purchases of one-time - * in-app items are consumable and thereafter can be purchased again. - * 5. An API to get current purchases of the user immediately. This will not contain any - * consumed purchases. - * - * All calls will give a response code with the following possible values - * RESULT_OK = 0 - success - * RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog - * RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down - * RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested - * RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase - * RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API - * RESULT_ERROR = 6 - Fatal error during the API action - * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned - * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned - */ -public interface IInAppBillingService extends android.os.IInterface - { - /** Local-side IPC implementation stub class. */ - public static abstract class Stub extends android.os.Binder implements com.android.vending.billing.IInAppBillingService - { - private static final java.lang.String DESCRIPTOR = "com.android.vending.billing.IInAppBillingService"; - /** Construct the stub at attach it to the interface. */ - public Stub() - { - this.attachInterface(this, DESCRIPTOR); - } - /** - * Cast an IBinder object into an com.android.vending.billing.IInAppBillingService interface, - * generating a proxy if needed. - */ - public static com.android.vending.billing.IInAppBillingService asInterface(android.os.IBinder obj) - { - if ((obj==null)) { - return null; - } - android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); - if (((iin!=null)&&(iin instanceof com.android.vending.billing.IInAppBillingService))) { - return ((com.android.vending.billing.IInAppBillingService)iin); - } - return new com.android.vending.billing.IInAppBillingService.Stub.Proxy(obj); - } - @Override public android.os.IBinder asBinder() - { - return this; - } - @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException - { - switch (code) - { - case INTERFACE_TRANSACTION: - { - reply.writeString(DESCRIPTOR); - return true; - } - case TRANSACTION_isBillingSupported: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - int _result = this.isBillingSupported(_arg0, _arg1, _arg2); - reply.writeNoException(); - reply.writeInt(_result); - return true; - } - case TRANSACTION_getSkuDetails: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - android.os.Bundle _arg3; - if ((0!=data.readInt())) { - _arg3 = android.os.Bundle.CREATOR.createFromParcel(data); - } - else { - _arg3 = null; - } - android.os.Bundle _result = this.getSkuDetails(_arg0, _arg1, _arg2, _arg3); - reply.writeNoException(); - if ((_result!=null)) { - reply.writeInt(1); - _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - } - else { - reply.writeInt(0); - } - return true; - } - case TRANSACTION_getBuyIntent: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - java.lang.String _arg3; - _arg3 = data.readString(); - java.lang.String _arg4; - _arg4 = data.readString(); - android.os.Bundle _result = this.getBuyIntent(_arg0, _arg1, _arg2, _arg3, _arg4); - reply.writeNoException(); - if ((_result!=null)) { - reply.writeInt(1); - _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - } - else { - reply.writeInt(0); - } - return true; - } - case TRANSACTION_getPurchases: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - java.lang.String _arg3; - _arg3 = data.readString(); - android.os.Bundle _result = this.getPurchases(_arg0, _arg1, _arg2, _arg3); - reply.writeNoException(); - if ((_result!=null)) { - reply.writeInt(1); - _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - } - else { - reply.writeInt(0); - } - return true; - } - case TRANSACTION_consumePurchase: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - int _result = this.consumePurchase(_arg0, _arg1, _arg2); - reply.writeNoException(); - reply.writeInt(_result); - return true; - } - case TRANSACTION_stub: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - int _result = this.stub(_arg0, _arg1, _arg2); - reply.writeNoException(); - reply.writeInt(_result); - return true; - } - case TRANSACTION_getBuyIntentToReplaceSkus: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.util.List _arg2; - _arg2 = data.createStringArrayList(); - java.lang.String _arg3; - _arg3 = data.readString(); - java.lang.String _arg4; - _arg4 = data.readString(); - java.lang.String _arg5; - _arg5 = data.readString(); - android.os.Bundle _result = this.getBuyIntentToReplaceSkus(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); - reply.writeNoException(); - if ((_result!=null)) { - reply.writeInt(1); - _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - } - else { - reply.writeInt(0); - } - return true; - } - case TRANSACTION_getBuyIntentExtraParams: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - java.lang.String _arg3; - _arg3 = data.readString(); - java.lang.String _arg4; - _arg4 = data.readString(); - android.os.Bundle _arg5; - if ((0!=data.readInt())) { - _arg5 = android.os.Bundle.CREATOR.createFromParcel(data); - } - else { - _arg5 = null; - } - android.os.Bundle _result = this.getBuyIntentExtraParams(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); - reply.writeNoException(); - if ((_result!=null)) { - reply.writeInt(1); - _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - } - else { - reply.writeInt(0); - } - return true; - } - case TRANSACTION_getPurchaseHistory: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - java.lang.String _arg3; - _arg3 = data.readString(); - android.os.Bundle _arg4; - if ((0!=data.readInt())) { - _arg4 = android.os.Bundle.CREATOR.createFromParcel(data); - } - else { - _arg4 = null; - } - android.os.Bundle _result = this.getPurchaseHistory(_arg0, _arg1, _arg2, _arg3, _arg4); - reply.writeNoException(); - if ((_result!=null)) { - reply.writeInt(1); - _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); - } - else { - reply.writeInt(0); - } - return true; - } - case TRANSACTION_isBillingSupportedExtraParams: - { - data.enforceInterface(DESCRIPTOR); - int _arg0; - _arg0 = data.readInt(); - java.lang.String _arg1; - _arg1 = data.readString(); - java.lang.String _arg2; - _arg2 = data.readString(); - android.os.Bundle _arg3; - if ((0!=data.readInt())) { - _arg3 = android.os.Bundle.CREATOR.createFromParcel(data); - } - else { - _arg3 = null; - } - int _result = this.isBillingSupportedExtraParams(_arg0, _arg1, _arg2, _arg3); - reply.writeNoException(); - reply.writeInt(_result); - return true; - } - } - return super.onTransact(code, data, reply, flags); - } - private static class Proxy implements com.android.vending.billing.IInAppBillingService - { - private android.os.IBinder mRemote; - Proxy(android.os.IBinder remote) - { - mRemote = remote; - } - @Override public android.os.IBinder asBinder() - { - return mRemote; - } - public java.lang.String getInterfaceDescriptor() - { - return DESCRIPTOR; - } - @Override public int isBillingSupported(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(type); - mRemote.transact(Stub.TRANSACTION_isBillingSupported, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - /** - * Provides details of a list of SKUs - * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle - * with a list JSON strings containing the productId, price, title and description. - * This API can be called with a maximum of 20 SKUs. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type of the in-app items ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "DETAILS_LIST" with a StringArrayList containing purchase information - * in JSON format similar to: - * '{ "productId" : "exampleSku", - * "type" : "inapp", - * "price" : "$5.00", - * "price_currency": "USD", - * "price_amount_micros": 5000000, - * "title : "Example Title", - * "description" : "This is an example description" }' - */ - @Override public android.os.Bundle getSkuDetails(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle skusBundle) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - android.os.Bundle _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(type); - if ((skusBundle!=null)) { - _data.writeInt(1); - skusBundle.writeToParcel(_data, 0); - } - else { - _data.writeInt(0); - } - mRemote.transact(Stub.TRANSACTION_getSkuDetails, _data, _reply, 0); - _reply.readException(); - if ((0!=_reply.readInt())) { - _result = android.os.Bundle.CREATOR.createFromParcel(_reply); - } - else { - _result = null; - } - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - /** - * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, - * the type, a unique purchase token and an optional developer payload. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - @Override public android.os.Bundle getBuyIntent(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - android.os.Bundle _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(sku); - _data.writeString(type); - _data.writeString(developerPayload); - mRemote.transact(Stub.TRANSACTION_getBuyIntent, _data, _reply, 0); - _reply.readException(); - if ((0!=_reply.readInt())) { - _result = android.os.Bundle.CREATOR.createFromParcel(_reply); - } - else { - _result = null; - } - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - /** - * Returns the current SKUs owned by the user of the type and package name specified along with - * purchase information and a signature of the data to be validated. - * This will return all SKUs that have been purchased in V3 and managed items purchased using - * V1 and V2 that have not been consumed. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus are too many, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - on failures. - * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - @Override public android.os.Bundle getPurchases(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - android.os.Bundle _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(type); - _data.writeString(continuationToken); - mRemote.transact(Stub.TRANSACTION_getPurchases, _data, _reply, 0); - _reply.readException(); - if ((0!=_reply.readInt())) { - _result = android.os.Bundle.CREATOR.createFromParcel(_reply); - } - else { - _result = null; - } - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - @Override public int consumePurchase(int apiVersion, java.lang.String packageName, java.lang.String purchaseToken) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(purchaseToken); - mRemote.transact(Stub.TRANSACTION_consumePurchase, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - @Override public int stub(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(type); - mRemote.transact(Stub.TRANSACTION_stub, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - /** - * Returns a pending intent to launch the purchase flow for upgrading or downgrading a - * subscription. The existing owned SKU(s) should be provided along with the new SKU that - * the user is upgrading or downgrading to. - * @param apiVersion billing API version that the app is using, must be 5 or later - * @param packageName package name of the calling app - * @param oldSkus the SKU(s) that the user is upgrading or downgrading from, - * if null or empty this method will behave like {@link #getBuyIntent} - * @param newSku the SKU that the user is upgrading or downgrading to - * @param type of the item being purchased, currently must be "subs" - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - @Override public android.os.Bundle getBuyIntentToReplaceSkus(int apiVersion, java.lang.String packageName, java.util.List oldSkus, java.lang.String newSku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - android.os.Bundle _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeStringList(oldSkus); - _data.writeString(newSku); - _data.writeString(type); - _data.writeString(developerPayload); - mRemote.transact(Stub.TRANSACTION_getBuyIntentToReplaceSkus, _data, _reply, 0); - _reply.readException(); - if ((0!=_reply.readInt())) { - _result = android.os.Bundle.CREATOR.createFromParcel(_reply); - } - else { - _result = null; - } - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - /** - * Returns a pending intent to launch the purchase flow for an in-app item. This method is - * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} - * parameter. This parameter is a Bundle of optional keys and values that affect the - * operation of the method. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @extraParams a Bundle with the following optional keys: - * "skusToReplace" - List - an optional list of SKUs that the user is - * upgrading or downgrading from. - * Pass this field if the purchase is upgrading or downgrading - * existing subscriptions. - * The specified SKUs are replaced with the SKUs that the user is - * purchasing. Google Play replaces the specified SKUs at the start of - * the next billing cycle. - * "replaceSkusProration" - Boolean - whether the user should be credited for any unused - * subscription time on the SKUs they are upgrading or downgrading. - * If you set this field to true, Google Play swaps out the old SKUs - * and credits the user with the unused value of their subscription - * time on a pro-rated basis. - * Google Play applies this credit to the new subscription, and does - * not begin billing the user for the new subscription until after - * the credit is used up. - * If you set this field to false, the user does not receive credit for - * any unused subscription time and the recurrence date does not - * change. - * Default value is true. Ignored if you do not pass skusToReplace. - * "accountId" - String - an optional obfuscated string that is uniquely - * associated with the user's account in your app. - * If you pass this value, Google Play can use it to detect irregular - * activity, such as many devices making purchases on the same - * account in a short period of time. - * Do not use the developer ID or the user's Google ID for this field. - * In addition, this field should not contain the user's ID in - * cleartext. - * We recommend that you use a one-way hash to generate a string from - * the user's ID, and store the hashed string in this field. - * "vr" - Boolean - an optional flag indicating whether the returned intent - * should start a VR purchase flow. The apiVersion must also be 7 or - * later to use this flag. - */ - @Override public android.os.Bundle getBuyIntentExtraParams(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload, android.os.Bundle extraParams) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - android.os.Bundle _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(sku); - _data.writeString(type); - _data.writeString(developerPayload); - if ((extraParams!=null)) { - _data.writeInt(1); - extraParams.writeToParcel(_data, 0); - } - else { - _data.writeInt(0); - } - mRemote.transact(Stub.TRANSACTION_getBuyIntentExtraParams, _data, _reply, 0); - _reply.readException(); - if ((0!=_reply.readInt())) { - _result = android.os.Bundle.CREATOR.createFromParcel(_reply); - } - else { - _result = null; - } - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - /** - * Returns the most recent purchase made by the user for each SKU, even if that purchase is - * expired, canceled, or consumed. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus is too large, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @param extraParams a Bundle with extra params that would be appended into http request - * query string. Not used at this moment. Reserved for future functionality. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value: RESULT_OK(0) if success, - * {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. - * - * "INAPP_PURCHASE_ITEM_LIST" - ArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - ArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- ArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - @Override public android.os.Bundle getPurchaseHistory(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken, android.os.Bundle extraParams) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - android.os.Bundle _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(type); - _data.writeString(continuationToken); - if ((extraParams!=null)) { - _data.writeInt(1); - extraParams.writeToParcel(_data, 0); - } - else { - _data.writeInt(0); - } - mRemote.transact(Stub.TRANSACTION_getPurchaseHistory, _data, _reply, 0); - _reply.readException(); - if ((0!=_reply.readInt())) { - _result = android.os.Bundle.CREATOR.createFromParcel(_reply); - } - else { - _result = null; - } - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - @Override public int isBillingSupportedExtraParams(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle extraParams) throws android.os.RemoteException - { - android.os.Parcel _data = android.os.Parcel.obtain(); - android.os.Parcel _reply = android.os.Parcel.obtain(); - int _result; - try { - _data.writeInterfaceToken(DESCRIPTOR); - _data.writeInt(apiVersion); - _data.writeString(packageName); - _data.writeString(type); - if ((extraParams!=null)) { - _data.writeInt(1); - extraParams.writeToParcel(_data, 0); - } - else { - _data.writeInt(0); - } - mRemote.transact(Stub.TRANSACTION_isBillingSupportedExtraParams, _data, _reply, 0); - _reply.readException(); - _result = _reply.readInt(); - } - finally { - _reply.recycle(); - _data.recycle(); - } - return _result; - } - } - static final int TRANSACTION_isBillingSupported = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); - static final int TRANSACTION_getSkuDetails = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); - static final int TRANSACTION_getBuyIntent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); - static final int TRANSACTION_getPurchases = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); - static final int TRANSACTION_consumePurchase = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4); - static final int TRANSACTION_stub = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5); - static final int TRANSACTION_getBuyIntentToReplaceSkus = (android.os.IBinder.FIRST_CALL_TRANSACTION + 6); - static final int TRANSACTION_getBuyIntentExtraParams = (android.os.IBinder.FIRST_CALL_TRANSACTION + 7); - static final int TRANSACTION_getPurchaseHistory = (android.os.IBinder.FIRST_CALL_TRANSACTION + 8); - static final int TRANSACTION_isBillingSupportedExtraParams = (android.os.IBinder.FIRST_CALL_TRANSACTION + 9); - } - public int isBillingSupported(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException; - /** - * Provides details of a list of SKUs - * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle - * with a list JSON strings containing the productId, price, title and description. - * This API can be called with a maximum of 20 SKUs. - * @param apiVersion billing API version that the app is using - * @param packageName the package name of the calling app - * @param type of the in-app items ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "DETAILS_LIST" with a StringArrayList containing purchase information - * in JSON format similar to: - * '{ "productId" : "exampleSku", - * "type" : "inapp", - * "price" : "$5.00", - * "price_currency": "USD", - * "price_amount_micros": 5000000, - * "title : "Example Title", - * "description" : "This is an example description" }' - */ - public android.os.Bundle getSkuDetails(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle skusBundle) throws android.os.RemoteException; - /** - * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, - * the type, a unique purchase token and an optional developer payload. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - public android.os.Bundle getBuyIntent(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException; - /** - * Returns the current SKUs owned by the user of the type and package name specified along with - * purchase information and a signature of the data to be validated. - * This will return all SKUs that have been purchased in V3 and managed items purchased using - * V1 and V2 that have not been consumed. - * @param apiVersion billing API version that the app is using - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus are too many, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - on failures. - * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - public android.os.Bundle getPurchases(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken) throws android.os.RemoteException; - public int consumePurchase(int apiVersion, java.lang.String packageName, java.lang.String purchaseToken) throws android.os.RemoteException; - public int stub(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException; - /** - * Returns a pending intent to launch the purchase flow for upgrading or downgrading a - * subscription. The existing owned SKU(s) should be provided along with the new SKU that - * the user is upgrading or downgrading to. - * @param apiVersion billing API version that the app is using, must be 5 or later - * @param packageName package name of the calling app - * @param oldSkus the SKU(s) that the user is upgrading or downgrading from, - * if null or empty this method will behave like {@link #getBuyIntent} - * @param newSku the SKU that the user is upgrading or downgrading to - * @param type of the item being purchased, currently must be "subs" - * @param developerPayload optional argument to be sent back with the purchase information - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes - * on failures. - * "BUY_INTENT" - PendingIntent to start the purchase flow - * - * The Pending intent should be launched with startIntentSenderForResult. When purchase flow - * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. - * If the purchase is successful, the result data will contain the following key-value pairs - * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response - * codes on failures. - * "INAPP_PURCHASE_DATA" - String in JSON format similar to - * '{"orderId":"12999763169054705758.1371079406387615", - * "packageName":"com.example.app", - * "productId":"exampleSku", - * "purchaseTime":1345678900000, - * "purchaseToken" : "122333444455555", - * "developerPayload":"example developer payload" }' - * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that - * was signed with the private key of the developer - */ - public android.os.Bundle getBuyIntentToReplaceSkus(int apiVersion, java.lang.String packageName, java.util.List oldSkus, java.lang.String newSku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException; - /** - * Returns a pending intent to launch the purchase flow for an in-app item. This method is - * a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} - * parameter. This parameter is a Bundle of optional keys and values that affect the - * operation of the method. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param sku the SKU of the in-app item as published in the developer console - * @param type of the in-app item being purchased ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param developerPayload optional argument to be sent back with the purchase information - * @extraParams a Bundle with the following optional keys: - * "skusToReplace" - List - an optional list of SKUs that the user is - * upgrading or downgrading from. - * Pass this field if the purchase is upgrading or downgrading - * existing subscriptions. - * The specified SKUs are replaced with the SKUs that the user is - * purchasing. Google Play replaces the specified SKUs at the start of - * the next billing cycle. - * "replaceSkusProration" - Boolean - whether the user should be credited for any unused - * subscription time on the SKUs they are upgrading or downgrading. - * If you set this field to true, Google Play swaps out the old SKUs - * and credits the user with the unused value of their subscription - * time on a pro-rated basis. - * Google Play applies this credit to the new subscription, and does - * not begin billing the user for the new subscription until after - * the credit is used up. - * If you set this field to false, the user does not receive credit for - * any unused subscription time and the recurrence date does not - * change. - * Default value is true. Ignored if you do not pass skusToReplace. - * "accountId" - String - an optional obfuscated string that is uniquely - * associated with the user's account in your app. - * If you pass this value, Google Play can use it to detect irregular - * activity, such as many devices making purchases on the same - * account in a short period of time. - * Do not use the developer ID or the user's Google ID for this field. - * In addition, this field should not contain the user's ID in - * cleartext. - * We recommend that you use a one-way hash to generate a string from - * the user's ID, and store the hashed string in this field. - * "vr" - Boolean - an optional flag indicating whether the returned intent - * should start a VR purchase flow. The apiVersion must also be 7 or - * later to use this flag. - */ - public android.os.Bundle getBuyIntentExtraParams(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload, android.os.Bundle extraParams) throws android.os.RemoteException; - /** - * Returns the most recent purchase made by the user for each SKU, even if that purchase is - * expired, canceled, or consumed. - * @param apiVersion billing API version that the app is using, must be 6 or later - * @param packageName package name of the calling app - * @param type of the in-app items being requested ("inapp" for one-time purchases - * and "subs" for subscriptions) - * @param continuationToken to be set as null for the first call, if the number of owned - * skus is too large, a continuationToken is returned in the response bundle. - * This method can be called again with the continuation token to get the next set of - * owned skus. - * @param extraParams a Bundle with extra params that would be appended into http request - * query string. Not used at this moment. Reserved for future functionality. - * @return Bundle containing the following key-value pairs - * "RESPONSE_CODE" with int value: RESULT_OK(0) if success, - * {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. - * - * "INAPP_PURCHASE_ITEM_LIST" - ArrayList containing the list of SKUs - * "INAPP_PURCHASE_DATA_LIST" - ArrayList containing the purchase information - * "INAPP_DATA_SIGNATURE_LIST"- ArrayList containing the signatures - * of the purchase information - * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the - * next set of in-app purchases. Only set if the - * user has more owned skus than the current list. - */ - public android.os.Bundle getPurchaseHistory(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken, android.os.Bundle extraParams) throws android.os.RemoteException; - public int isBillingSupportedExtraParams(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle extraParams) throws android.os.RemoteException; - } diff --git a/modules/juce_core/native/java/JuceAppActivity.java b/modules/juce_core/native/java/JuceAppActivity.java deleted file mode 100644 index 28939a2f9c..0000000000 --- a/modules/juce_core/native/java/JuceAppActivity.java +++ /dev/null @@ -1,1806 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -package com.juce; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -$$JuceAndroidCameraImports$$ // If you get an error here, you need to re-save your project with the Projucer! -$$JuceAndroidVideoImports$$ // If you get an error here, you need to re-save your project with the Projucer! -import android.net.http.SslError; -import android.net.Uri; -import android.os.Bundle; -import android.os.Looper; -import android.os.Handler; -import android.os.Message; -import android.os.ParcelUuid; -import android.os.Environment; -import android.view.*; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.graphics.*; -import android.text.ClipboardManager; -import android.text.InputType; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.Pair; -import android.webkit.SslErrorHandler; -import android.webkit.WebChromeClient; -$$JuceAndroidWebViewImports$$ // If you get an error here, you need to re-save your project with the Projucer! -import android.webkit.WebView; -import android.webkit.WebViewClient; -import java.lang.Runnable; -import java.lang.ref.WeakReference; -import java.lang.reflect.*; -import java.util.*; -import java.io.*; -import java.net.URL; -import java.net.HttpURLConnection; -import android.media.AudioManager; -import android.Manifest; -import java.util.concurrent.CancellationException; -import java.util.concurrent.Future; -import java.util.concurrent.Executors; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.atomic.*; -$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the Projucer! - - -//============================================================================== -public class JuceAppActivity extends $$JuceAppActivityBaseClass$$ -{ - //============================================================================== - static - { - System.loadLibrary ("juce_jni"); - } - - //============================================================================== - public boolean isPermissionDeclaredInManifest (int permissionID) - { - return isPermissionDeclaredInManifest (getAndroidPermissionName (permissionID)); - } - - public boolean isPermissionDeclaredInManifest (String permissionToCheck) - { - try - { - PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS); - - if (info.requestedPermissions != null) - for (String permission : info.requestedPermissions) - if (permission.equals (permissionToCheck)) - return true; - } - catch (PackageManager.NameNotFoundException e) - { - Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString()); - } - - Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck); - return false; - } - - //============================================================================== - // these have to match the values of enum PermissionID in C++ class RuntimePermissions: - private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1; - private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2; - private static final int JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE = 3; - private static final int JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 4; - private static final int JUCE_PERMISSIONS_CAMERA = 5; - - private static String getAndroidPermissionName (int permissionID) - { - switch (permissionID) - { - case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO; - case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION; - // use string value as this is not defined in SDKs < 16 - case JUCE_PERMISSIONS_READ_EXTERNAL_STORAGE: return "android.permission.READ_EXTERNAL_STORAGE"; - case JUCE_PERMISSIONS_WRITE_EXTERNAL_STORAGE: return Manifest.permission.WRITE_EXTERNAL_STORAGE; - case JUCE_PERMISSIONS_CAMERA: return Manifest.permission.CAMERA; - } - - // unknown permission ID! - assert false; - return new String(); - } - - public boolean isPermissionGranted (int permissionID) - { - return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED; - } - - private Map permissionCallbackPtrMap; - - public void requestRuntimePermission (int permissionID, long ptrToCallback) - { - String permissionName = getAndroidPermissionName (permissionID); - - if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED) - { - // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously - permissionCallbackPtrMap.put (permissionID, ptrToCallback); - requestPermissionsCompat (new String[]{permissionName}, permissionID); - } - else - { - // permissions were already granted before, we can call callback directly - androidRuntimePermissionsCallback (true, ptrToCallback); - } - } - - private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback); - - $$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the Projucer! - - //============================================================================== - public interface JuceMidiPort - { - boolean isInputPort(); - - // start, stop does nothing on an output port - void start(); - void stop(); - - void close(); - - // send will do nothing on an input port - void sendMidi (byte[] msg, int offset, int count); - } - - //============================================================================== - $$JuceAndroidMidiCode$$ // If you get an error here, you need to re-save your project with the Projucer! - - //============================================================================== - @Override - public void onCreate (Bundle savedInstanceState) - { - super.onCreate (savedInstanceState); - - isScreenSaverEnabled = true; - hideActionBar(); - viewHolder = new ViewHolder (this); - setContentView (viewHolder); - - setVolumeControlStream (AudioManager.STREAM_MUSIC); - - permissionCallbackPtrMap = new HashMap(); - appPausedResumedListeners = new HashMap(); - } - - @Override - protected void onDestroy() - { - quitApp(); - super.onDestroy(); - - clearDataCache(); - } - - @Override - protected void onPause() - { - suspendApp(); - - Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]); - - for (Long k : keys) - appPausedResumedListeners.get (k).appPaused(); - - try - { - Thread.sleep (1000); // This is a bit of a hack to avoid some hard-to-track-down - // openGL glitches when pausing/resuming apps.. - } catch (InterruptedException e) {} - - super.onPause(); - } - - @Override - protected void onResume() - { - super.onResume(); - resumeApp(); - - Long[] keys = appPausedResumedListeners.keySet().toArray (new Long[appPausedResumedListeners.keySet().size()]); - - for (Long k : keys) - appPausedResumedListeners.get (k).appResumed(); - } - - @Override - public void onConfigurationChanged (Configuration cfg) - { - super.onConfigurationChanged (cfg); - setContentView (viewHolder); - } - - private void callAppLauncher() - { - launchApp (getApplicationInfo().publicSourceDir, - getApplicationInfo().dataDir); - } - - // Need to override this as the default implementation always finishes the activity. - @Override - public void onBackPressed() - { - ComponentPeerView focusedView = getViewWithFocusOrDefaultView(); - - if (focusedView == null) - return; - - focusedView.backButtonPressed(); - } - - private ComponentPeerView getViewWithFocusOrDefaultView() - { - for (int i = 0; i < viewHolder.getChildCount(); ++i) - { - if (viewHolder.getChildAt (i).hasFocus()) - return (ComponentPeerView) viewHolder.getChildAt (i); - } - - if (viewHolder.getChildCount() > 0) - return (ComponentPeerView) viewHolder.getChildAt (0); - - return null; - } - - //============================================================================== - private void hideActionBar() - { - // get "getActionBar" method - java.lang.reflect.Method getActionBarMethod = null; - try - { - getActionBarMethod = this.getClass().getMethod ("getActionBar"); - } - catch (SecurityException e) { return; } - catch (NoSuchMethodException e) { return; } - if (getActionBarMethod == null) return; - - // invoke "getActionBar" method - Object actionBar = null; - try - { - actionBar = getActionBarMethod.invoke (this); - } - catch (java.lang.IllegalArgumentException e) { return; } - catch (java.lang.IllegalAccessException e) { return; } - catch (java.lang.reflect.InvocationTargetException e) { return; } - if (actionBar == null) return; - - // get "hide" method - java.lang.reflect.Method actionBarHideMethod = null; - try - { - actionBarHideMethod = actionBar.getClass().getMethod ("hide"); - } - catch (SecurityException e) { return; } - catch (NoSuchMethodException e) { return; } - if (actionBarHideMethod == null) return; - - // invoke "hide" method - try - { - actionBarHideMethod.invoke (actionBar); - } - catch (java.lang.IllegalArgumentException e) {} - catch (java.lang.IllegalAccessException e) {} - catch (java.lang.reflect.InvocationTargetException e) {} - } - - void requestPermissionsCompat (String[] permissions, int requestCode) - { - Method requestPermissionsMethod = null; - try - { - requestPermissionsMethod = this.getClass().getMethod ("requestPermissions", - String[].class, int.class); - } - catch (SecurityException e) { return; } - catch (NoSuchMethodException e) { return; } - if (requestPermissionsMethod == null) return; - - try - { - requestPermissionsMethod.invoke (this, permissions, requestCode); - } - catch (java.lang.IllegalArgumentException e) {} - catch (java.lang.IllegalAccessException e) {} - catch (java.lang.reflect.InvocationTargetException e) {} - } - - //============================================================================== - private native void launchApp (String appFile, String appDataDir); - private native void quitApp(); - private native void suspendApp(); - private native void resumeApp(); - private native void setScreenSize (int screenWidth, int screenHeight, int dpi); - private native void appActivityResult (int requestCode, int resultCode, Intent data); - private native void appNewIntent (Intent intent); - - //============================================================================== - private ViewHolder viewHolder; - private MidiDeviceManager midiDeviceManager = null; - private BluetoothManager bluetoothManager = null; - private boolean isScreenSaverEnabled; - private java.util.Timer keepAliveTimer; - - public final ComponentPeerView createNewView (boolean opaque, long host) - { - ComponentPeerView v = new ComponentPeerView (this, opaque, host); - viewHolder.addView (v); - addAppPausedResumedListener (v, host); - return v; - } - - public final void deleteView (ComponentPeerView view) - { - removeAppPausedResumedListener (view, view.host); - - view.host = 0; - - ViewGroup group = (ViewGroup) (view.getParent()); - - if (group != null) - group.removeView (view); - } - - public final void deleteNativeSurfaceView (NativeSurfaceView view) - { - ViewGroup group = (ViewGroup) (view.getParent()); - - if (group != null) - group.removeView (view); - } - - final class ViewHolder extends ViewGroup - { - public ViewHolder (Context context) - { - super (context); - setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS); - setFocusable (false); - } - - protected final void onLayout (boolean changed, int left, int top, int right, int bottom) - { - setScreenSize (getWidth(), getHeight(), getDPI()); - - if (isFirstResize) - { - isFirstResize = false; - callAppLauncher(); - } - } - - private final int getDPI() - { - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics (metrics); - return metrics.densityDpi; - } - - private boolean isFirstResize = true; - } - - public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom) - { - canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE); - } - - //============================================================================== - public final void setScreenSaver (boolean enabled) - { - if (isScreenSaverEnabled != enabled) - { - isScreenSaverEnabled = enabled; - - if (keepAliveTimer != null) - { - keepAliveTimer.cancel(); - keepAliveTimer = null; - } - - if (enabled) - { - getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - else - { - getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - - // If no user input is received after about 3 seconds, the OS will lower the - // task's priority, so this timer forces it to be kept active. - keepAliveTimer = new java.util.Timer(); - - keepAliveTimer.scheduleAtFixedRate (new TimerTask() - { - @Override - public void run() - { - android.app.Instrumentation instrumentation = new android.app.Instrumentation(); - - try - { - instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN); - } - catch (Exception e) - { - } - } - }, 2000, 2000); - } - } - } - - public final boolean getScreenSaver() - { - return isScreenSaverEnabled; - } - - //============================================================================== - public final String getClipboardContent() - { - ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE); - - CharSequence content = clipboard.getText(); - return content != null ? content.toString() : new String(); - } - - public final void setClipboardContent (String newText) - { - ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE); - clipboard.setText (newText); - } - - //============================================================================== - public final void showMessageBox (String title, String message, final long callback) - { - AlertDialog.Builder builder = new AlertDialog.Builder (this); - builder.setTitle (title) - .setMessage (message) - .setCancelable (true) - .setOnCancelListener (new DialogInterface.OnCancelListener() - { - public void onCancel (DialogInterface dialog) - { - JuceAppActivity.this.alertDismissed (callback, 0); - } - }) - .setPositiveButton ("OK", new DialogInterface.OnClickListener() - { - public void onClick (DialogInterface dialog, int id) - { - dialog.dismiss(); - JuceAppActivity.this.alertDismissed (callback, 0); - } - }); - - builder.create().show(); - } - - public final void showOkCancelBox (String title, String message, final long callback, - String okButtonText, String cancelButtonText) - { - AlertDialog.Builder builder = new AlertDialog.Builder (this); - builder.setTitle (title) - .setMessage (message) - .setCancelable (true) - .setOnCancelListener (new DialogInterface.OnCancelListener() - { - public void onCancel (DialogInterface dialog) - { - JuceAppActivity.this.alertDismissed (callback, 0); - } - }) - .setPositiveButton (okButtonText.isEmpty() ? "OK" : okButtonText, new DialogInterface.OnClickListener() - { - public void onClick (DialogInterface dialog, int id) - { - dialog.dismiss(); - JuceAppActivity.this.alertDismissed (callback, 1); - } - }) - .setNegativeButton (cancelButtonText.isEmpty() ? "Cancel" : cancelButtonText, new DialogInterface.OnClickListener() - { - public void onClick (DialogInterface dialog, int id) - { - dialog.dismiss(); - JuceAppActivity.this.alertDismissed (callback, 0); - } - }); - - builder.create().show(); - } - - public final void showYesNoCancelBox (String title, String message, final long callback) - { - AlertDialog.Builder builder = new AlertDialog.Builder (this); - builder.setTitle (title) - .setMessage (message) - .setCancelable (true) - .setOnCancelListener (new DialogInterface.OnCancelListener() - { - public void onCancel (DialogInterface dialog) - { - JuceAppActivity.this.alertDismissed (callback, 0); - } - }) - .setPositiveButton ("Yes", new DialogInterface.OnClickListener() - { - public void onClick (DialogInterface dialog, int id) - { - dialog.dismiss(); - JuceAppActivity.this.alertDismissed (callback, 1); - } - }) - .setNegativeButton ("No", new DialogInterface.OnClickListener() - { - public void onClick (DialogInterface dialog, int id) - { - dialog.dismiss(); - JuceAppActivity.this.alertDismissed (callback, 2); - } - }) - .setNeutralButton ("Cancel", new DialogInterface.OnClickListener() - { - public void onClick (DialogInterface dialog, int id) - { - dialog.dismiss(); - JuceAppActivity.this.alertDismissed (callback, 0); - } - }); - - builder.create().show(); - } - - public native void alertDismissed (long callback, int id); - - //============================================================================== - public interface AppPausedResumedListener - { - void appPaused(); - void appResumed(); - } - - private Map appPausedResumedListeners; - - public void addAppPausedResumedListener (AppPausedResumedListener l, long listenerHost) - { - appPausedResumedListeners.put (new Long (listenerHost), l); - } - - public void removeAppPausedResumedListener (AppPausedResumedListener l, long listenerHost) - { - appPausedResumedListeners.remove (new Long (listenerHost)); - } - - //============================================================================== - public final class ComponentPeerView extends ViewGroup - implements View.OnFocusChangeListener, AppPausedResumedListener - { - public ComponentPeerView (Context context, boolean opaque_, long host) - { - super (context); - this.host = host; - setWillNotDraw (false); - opaque = opaque_; - - setFocusable (true); - setFocusableInTouchMode (true); - setOnFocusChangeListener (this); - - // swap red and blue colours to match internal opengl texture format - ColorMatrix colorMatrix = new ColorMatrix(); - - float[] colorTransform = { 0, 0, 1.0f, 0, 0, - 0, 1.0f, 0, 0, 0, - 1.0f, 0, 0, 0, 0, - 0, 0, 0, 1.0f, 0 }; - - colorMatrix.set (colorTransform); - paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix)); - - java.lang.reflect.Method method = null; - - try - { - method = getClass().getMethod ("setLayerType", int.class, Paint.class); - } - catch (SecurityException e) {} - catch (NoSuchMethodException e) {} - - if (method != null) - { - try - { - int layerTypeNone = 0; - method.invoke (this, layerTypeNone, null); - } - catch (java.lang.IllegalArgumentException e) {} - catch (java.lang.IllegalAccessException e) {} - catch (java.lang.reflect.InvocationTargetException e) {} - } - } - - //============================================================================== - private native void handlePaint (long host, Canvas canvas, Paint paint); - - @Override - public void onDraw (Canvas canvas) - { - if (host == 0) - return; - - handlePaint (host, canvas, paint); - } - - @Override - public boolean isOpaque() - { - return opaque; - } - - private boolean opaque; - private long host; - private Paint paint = new Paint(); - - //============================================================================== - private native void handleMouseDown (long host, int index, float x, float y, long time); - private native void handleMouseDrag (long host, int index, float x, float y, long time); - private native void handleMouseUp (long host, int index, float x, float y, long time); - - @Override - public boolean onTouchEvent (MotionEvent event) - { - if (host == 0) - return false; - - int action = event.getAction(); - long time = event.getEventTime(); - - switch (action & MotionEvent.ACTION_MASK) - { - case MotionEvent.ACTION_DOWN: - handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time); - return true; - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time); - return true; - - case MotionEvent.ACTION_MOVE: - { - int n = event.getPointerCount(); - for (int i = 0; i < n; ++i) - handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time); - - return true; - } - - case MotionEvent.ACTION_POINTER_UP: - { - int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time); - return true; - } - - case MotionEvent.ACTION_POINTER_DOWN: - { - int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; - handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time); - return true; - } - - default: - break; - } - - return false; - } - - //============================================================================== - private native void handleKeyDown (long host, int keycode, int textchar); - private native void handleKeyUp (long host, int keycode, int textchar); - private native void handleBackButton (long host); - private native void handleKeyboardHidden (long host); - - public void showKeyboard (String type) - { - InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE); - - if (imm != null) - { - if (type.length() > 0) - { - imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); - imm.setInputMethod (getWindowToken(), type); - keyboardDismissListener.startListening(); - } - else - { - imm.hideSoftInputFromWindow (getWindowToken(), 0); - keyboardDismissListener.stopListening(); - } - } - } - - public void backButtonPressed() - { - if (host == 0) - return; - - handleBackButton (host); - } - - @Override - public boolean onKeyDown (int keyCode, KeyEvent event) - { - if (host == 0) - return false; - - switch (keyCode) - { - case KeyEvent.KEYCODE_VOLUME_UP: - case KeyEvent.KEYCODE_VOLUME_DOWN: - return super.onKeyDown (keyCode, event); - case KeyEvent.KEYCODE_BACK: - { - ((Activity) getContext()).onBackPressed(); - return true; - } - - default: - break; - } - - handleKeyDown (host, keyCode, event.getUnicodeChar()); - return true; - } - - @Override - public boolean onKeyUp (int keyCode, KeyEvent event) - { - if (host == 0) - return false; - - handleKeyUp (host, keyCode, event.getUnicodeChar()); - return true; - } - - @Override - public boolean onKeyMultiple (int keyCode, int count, KeyEvent event) - { - if (host == 0) - return false; - - if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE) - return super.onKeyMultiple (keyCode, count, event); - - if (event.getCharacters() != null) - { - int utf8Char = event.getCharacters().codePointAt (0); - handleKeyDown (host, utf8Char, utf8Char); - return true; - } - - return false; - } - - //============================================================================== - private final class KeyboardDismissListener - { - public KeyboardDismissListener (ComponentPeerView viewToUse) - { - view = viewToUse; - } - - private void startListening() - { - view.getViewTreeObserver().addOnGlobalLayoutListener(viewTreeObserver); - } - - private void stopListening() - { - view.getViewTreeObserver().removeGlobalOnLayoutListener(viewTreeObserver); - } - - private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener - { - TreeObserver() - { - keyboardShown = false; - } - - @Override - public void onGlobalLayout() - { - Rect r = new Rect(); - - ViewGroup parentView = (ViewGroup) getParent(); - - if (parentView == null) - return; - - parentView.getWindowVisibleDisplayFrame (r); - - int diff = parentView.getHeight() - (r.bottom - r.top); - - // Arbitrary threshold, surely keyboard would take more than 20 pix. - if (diff < 20 && keyboardShown) - { - keyboardShown = false; - handleKeyboardHidden (view.host); - } - - if (! keyboardShown && diff > 20) - keyboardShown = true; - }; - - private boolean keyboardShown; - }; - - private ComponentPeerView view; - private TreeObserver viewTreeObserver = new TreeObserver(); - } - - private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener(this); - - // this is here to make keyboard entry work on a Galaxy Tab2 10.1 - @Override - public InputConnection onCreateInputConnection (EditorInfo outAttrs) - { - outAttrs.actionLabel = ""; - outAttrs.hintText = ""; - outAttrs.initialCapsMode = 0; - outAttrs.initialSelEnd = outAttrs.initialSelStart = -1; - outAttrs.label = ""; - outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI; - outAttrs.inputType = InputType.TYPE_NULL; - - return new BaseInputConnection (this, false); - } - - //============================================================================== - @Override - protected void onSizeChanged (int w, int h, int oldw, int oldh) - { - if (host == 0) - return; - - super.onSizeChanged (w, h, oldw, oldh); - viewSizeChanged (host); - } - - @Override - protected void onLayout (boolean changed, int left, int top, int right, int bottom) - { - for (int i = getChildCount(); --i >= 0;) - requestTransparentRegion (getChildAt (i)); - } - - private native void viewSizeChanged (long host); - - @Override - public void onFocusChange (View v, boolean hasFocus) - { - if (host == 0) - return; - - if (v == this) - focusChanged (host, hasFocus); - } - - private native void focusChanged (long host, boolean hasFocus); - - 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); } - - public boolean containsPoint (int x, int y) - { - return true; //xxx needs to check overlapping views - } - - //============================================================================== - private native void handleAppPaused (long host); - private native void handleAppResumed (long host); - - @Override - public void appPaused() - { - if (host == 0) - return; - - handleAppPaused (host); - } - - @Override - public void appResumed() - { - if (host == 0) - return; - - // Ensure that navigation/status bar visibility is correctly restored. - handleAppResumed (host); - } - } - - //============================================================================== - public static class NativeSurfaceView extends SurfaceView - implements SurfaceHolder.Callback - { - private long nativeContext = 0; - private boolean forVideo; - - NativeSurfaceView (Context context, long nativeContextPtr, boolean createdForVideo) - { - super (context); - nativeContext = nativeContextPtr; - forVideo = createdForVideo; - } - - public Surface getNativeSurface() - { - Surface retval = null; - - SurfaceHolder holder = getHolder(); - if (holder != null) - retval = holder.getSurface(); - - return retval; - } - - //============================================================================== - @Override - public void surfaceChanged (SurfaceHolder holder, int format, int width, int height) - { - if (forVideo) - surfaceChangedNativeVideo (nativeContext, holder, format, width, height); - else - surfaceChangedNative (nativeContext, holder, format, width, height); - } - - @Override - public void surfaceCreated (SurfaceHolder holder) - { - if (forVideo) - surfaceCreatedNativeVideo (nativeContext, holder); - else - surfaceCreatedNative (nativeContext, holder); - } - - @Override - public void surfaceDestroyed (SurfaceHolder holder) - { - if (forVideo) - surfaceDestroyedNativeVideo (nativeContext, holder); - else - surfaceDestroyedNative (nativeContext, holder); - } - - @Override - protected void dispatchDraw (Canvas canvas) - { - super.dispatchDraw (canvas); - - if (forVideo) - dispatchDrawNativeVideo (nativeContext, canvas); - else - dispatchDrawNative (nativeContext, canvas); - } - - //============================================================================== - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - getHolder().addCallback (this); - } - - @Override - protected void onDetachedFromWindow() - { - super.onDetachedFromWindow(); - getHolder().removeCallback (this); - } - - //============================================================================== - private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas); - private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder); - private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder); - private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder, - int format, int width, int height); - - private native void dispatchDrawNativeVideo (long nativeContextPtr, Canvas canvas); - private native void surfaceCreatedNativeVideo (long nativeContextptr, SurfaceHolder holder); - private native void surfaceDestroyedNativeVideo (long nativeContextptr, SurfaceHolder holder); - private native void surfaceChangedNativeVideo (long nativeContextptr, SurfaceHolder holder, - int format, int width, int height); - } - - public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr, boolean forVideo) - { - return new NativeSurfaceView (this, nativeSurfacePtr, forVideo); - } - - //============================================================================== - public final int[] renderGlyph (char glyph1, char glyph2, Paint paint, android.graphics.Matrix matrix, Rect bounds) - { - Path p = new Path(); - - char[] str = { glyph1, glyph2 }; - paint.getTextPath (str, 0, (glyph2 != 0 ? 2 : 1), 0.0f, 0.0f, p); - - RectF boundsF = new RectF(); - p.computeBounds (boundsF, true); - matrix.mapRect (boundsF); - - boundsF.roundOut (bounds); - bounds.left--; - bounds.right++; - - final int w = bounds.width(); - final int h = Math.max (1, bounds.height()); - - Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888); - - Canvas c = new Canvas (bm); - matrix.postTranslate (-bounds.left, -bounds.top); - c.setMatrix (matrix); - c.drawPath (p, paint); - - final int sizeNeeded = w * h; - if (cachedRenderArray.length < sizeNeeded) - cachedRenderArray = new int [sizeNeeded]; - - bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h); - bm.recycle(); - return cachedRenderArray; - } - - private int[] cachedRenderArray = new int [256]; - - //============================================================================== - public static class NativeInvocationHandler implements InvocationHandler - { - public NativeInvocationHandler (Activity activityToUse, long nativeContextRef) - { - activity = activityToUse; - nativeContext = nativeContextRef; - } - - public void nativeContextDeleted() - { - nativeContext = 0; - } - - @Override - public void finalize() - { - activity.runOnUiThread (new Runnable() - { - @Override - public void run() - { - if (nativeContext != 0) - dispatchFinalize (nativeContext); - } - }); - } - - @Override - public Object invoke (Object proxy, Method method, Object[] args) throws Throwable - { - return dispatchInvoke (nativeContext, proxy, method, args); - } - - //============================================================================== - Activity activity; - private long nativeContext = 0; - - private native void dispatchFinalize (long nativeContextRef); - private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args); - } - - public InvocationHandler createInvocationHandler (long nativeContextRef) - { - return new NativeInvocationHandler (this, nativeContextRef); - } - - public void invocationHandlerContextDeleted (InvocationHandler handler) - { - ((NativeInvocationHandler) handler).nativeContextDeleted(); - } - - //============================================================================== - public static class HTTPStream - { - public HTTPStream (String address, boolean isPostToUse, byte[] postDataToUse, - String headersToUse, int timeOutMsToUse, - int[] statusCodeToUse, StringBuffer responseHeadersToUse, - int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException - { - isPost = isPostToUse; - postData = postDataToUse; - headers = headersToUse; - timeOutMs = timeOutMsToUse; - statusCode = statusCodeToUse; - responseHeaders = responseHeadersToUse; - totalLength = -1; - numRedirectsToFollow = numRedirectsToFollowToUse; - httpRequestCmd = httpRequestCmdToUse; - - connection = createConnection (address, isPost, postData, headers, timeOutMs, httpRequestCmd); - } - - private final HttpURLConnection createConnection (String address, boolean isPost, byte[] postData, - String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException - { - HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection()); - - try - { - newConnection.setInstanceFollowRedirects (false); - newConnection.setConnectTimeout (timeOutMs); - newConnection.setReadTimeout (timeOutMs); - - // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines. - // So convert headers string to an array, with an element for each line - String headerLines[] = headers.split("\\n"); - - // Set request headers - for (int i = 0; i < headerLines.length; ++i) - { - int pos = headerLines[i].indexOf (":"); - - if (pos > 0 && pos < headerLines[i].length()) - { - String field = headerLines[i].substring (0, pos); - String value = headerLines[i].substring (pos + 1); - - if (value.length() > 0) - newConnection.setRequestProperty (field, value); - } - } - - newConnection.setRequestMethod (httpRequestCmd); - - if (isPost) - { - newConnection.setDoOutput (true); - - if (postData != null) - { - OutputStream out = newConnection.getOutputStream(); - out.write(postData); - out.flush(); - } - } - - return newConnection; - } - catch (Throwable e) - { - newConnection.disconnect(); - throw new IOException ("Connection error"); - } - } - - private final InputStream getCancellableStream (final boolean isInput) throws ExecutionException - { - synchronized (createFutureLock) - { - if (hasBeenCancelled.get()) - return null; - - streamFuture = executor.submit (new Callable() - { - @Override - public BufferedInputStream call() throws IOException - { - return new BufferedInputStream (isInput ? connection.getInputStream() - : connection.getErrorStream()); - } - }); - } - - try - { - return streamFuture.get(); - } - catch (InterruptedException e) - { - return null; - } - catch (CancellationException e) - { - return null; - } - } - - public final boolean connect() - { - boolean result = false; - int numFollowedRedirects = 0; - - while (true) - { - result = doConnect(); - - if (! result) - return false; - - if (++numFollowedRedirects > numRedirectsToFollow) - break; - - int status = statusCode[0]; - - if (status == 301 || status == 302 || status == 303 || status == 307) - { - // Assumes only one occurrence of "Location" - int pos1 = responseHeaders.indexOf ("Location:") + 10; - int pos2 = responseHeaders.indexOf ("\n", pos1); - - if (pos2 > pos1) - { - String currentLocation = connection.getURL().toString(); - String newLocation = responseHeaders.substring (pos1, pos2); - - try - { - // Handle newLocation whether it's absolute or relative - URL baseUrl = new URL (currentLocation); - URL newUrl = new URL (baseUrl, newLocation); - String transformedNewLocation = newUrl.toString(); - - if (transformedNewLocation != currentLocation) - { - // Clear responseHeaders before next iteration - responseHeaders.delete (0, responseHeaders.length()); - - synchronized (createStreamLock) - { - if (hasBeenCancelled.get()) - return false; - - connection.disconnect(); - - try - { - connection = createConnection (transformedNewLocation, isPost, - postData, headers, timeOutMs, - httpRequestCmd); - } - catch (Throwable e) - { - return false; - } - } - } - else - { - break; - } - } - catch (Throwable e) - { - return false; - } - } - else - { - break; - } - } - else - { - break; - } - } - - return result; - } - - private final boolean doConnect() - { - synchronized (createStreamLock) - { - if (hasBeenCancelled.get()) - return false; - - try - { - try - { - inputStream = getCancellableStream (true); - } - catch (ExecutionException e) - { - if (connection.getResponseCode() < 400) - { - statusCode[0] = connection.getResponseCode(); - connection.disconnect(); - return false; - } - } - finally - { - statusCode[0] = connection.getResponseCode(); - } - - try - { - if (statusCode[0] >= 400) - inputStream = getCancellableStream (false); - else - inputStream = getCancellableStream (true); - } - catch (ExecutionException e) - {} - - for (java.util.Map.Entry> entry : connection.getHeaderFields().entrySet()) - { - if (entry.getKey() != null && entry.getValue() != null) - { - responseHeaders.append(entry.getKey() + ": " - + android.text.TextUtils.join(",", entry.getValue()) + "\n"); - - if (entry.getKey().compareTo ("Content-Length") == 0) - totalLength = Integer.decode (entry.getValue().get (0)); - } - } - - return true; - } - catch (IOException e) - { - return false; - } - } - } - - static class DisconnectionRunnable implements Runnable - { - public DisconnectionRunnable (HttpURLConnection theConnection, - InputStream theInputStream, - ReentrantLock theCreateStreamLock, - Object theCreateFutureLock, - Future theStreamFuture) - { - connectionToDisconnect = theConnection; - inputStream = theInputStream; - createStreamLock = theCreateStreamLock; - createFutureLock = theCreateFutureLock; - streamFuture = theStreamFuture; - } - - public void run() - { - try - { - if (! createStreamLock.tryLock()) - { - synchronized (createFutureLock) - { - if (streamFuture != null) - streamFuture.cancel (true); - } - - createStreamLock.lock(); - } - - if (connectionToDisconnect != null) - connectionToDisconnect.disconnect(); - - if (inputStream != null) - inputStream.close(); - } - catch (IOException e) - {} - finally - { - createStreamLock.unlock(); - } - } - - private HttpURLConnection connectionToDisconnect; - private InputStream inputStream; - private ReentrantLock createStreamLock; - private Object createFutureLock; - Future streamFuture; - } - - public final void release() - { - DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable (connection, - inputStream, - createStreamLock, - createFutureLock, - streamFuture); - - synchronized (createStreamLock) - { - hasBeenCancelled.set (true); - - connection = null; - } - - Thread disconnectionThread = new Thread(disconnectionRunnable); - disconnectionThread.start(); - } - - public final int read (byte[] buffer, int numBytes) - { - int num = 0; - - try - { - synchronized (createStreamLock) - { - if (inputStream != null) - num = inputStream.read (buffer, 0, numBytes); - } - } - catch (IOException e) - {} - - if (num > 0) - position += num; - - return num; - } - - public final long getPosition() { return position; } - public final long getTotalLength() { return totalLength; } - public final boolean isExhausted() { return false; } - public final boolean setPosition (long newPos) { return false; } - - private boolean isPost; - private byte[] postData; - private String headers; - private int timeOutMs; - String httpRequestCmd; - private HttpURLConnection connection; - private int[] statusCode; - private StringBuffer responseHeaders; - private int totalLength; - private int numRedirectsToFollow; - private InputStream inputStream; - private long position; - private final ReentrantLock createStreamLock = new ReentrantLock(); - private final Object createFutureLock = new Object(); - private AtomicBoolean hasBeenCancelled = new AtomicBoolean(); - - private final ExecutorService executor = Executors.newCachedThreadPool (Executors.defaultThreadFactory()); - Future streamFuture; - } - - public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData, - String headers, int timeOutMs, int[] statusCode, - StringBuffer responseHeaders, int numRedirectsToFollow, - String httpRequestCmd) - { - // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL) - if (timeOutMs < 0) - timeOutMs = 0; - else if (timeOutMs == 0) - timeOutMs = 30000; - - for (;;) - { - try - { - HTTPStream httpStream = new HTTPStream (address, isPost, postData, headers, - timeOutMs, statusCode, responseHeaders, - numRedirectsToFollow, httpRequestCmd); - - return httpStream; - } - catch (Throwable e) {} - - return null; - } - } - - public final void launchURL (String url) - { - startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url))); - } - - private native boolean webViewPageLoadStarted (long host, WebView view, String url); - private native void webViewPageLoadFinished (long host, WebView view, String url); -$$JuceAndroidWebViewNativeCode$$ // If you get an error here, you need to re-save your project with the Projucer! - private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error); - private native void webViewCloseWindowRequest (long host, WebView view); - private native void webViewCreateWindowRequest (long host, WebView view); - - //============================================================================== - public class JuceWebViewClient extends WebViewClient - { - public JuceWebViewClient (long hostToUse) - { - host = hostToUse; - } - - public void hostDeleted() - { - synchronized (hostLock) - { - host = 0; - } - } - - @Override - public void onPageFinished (WebView view, String url) - { - if (host == 0) - return; - - webViewPageLoadFinished (host, view, url); - } - - @Override - public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) - { - if (host == 0) - return; - - webViewReceivedSslError (host, view, handler, error); - } - $$JuceAndroidWebViewCode$$ // If you get an error here, you need to re-save your project with the Projucer! - - private long host; - private final Object hostLock = new Object(); - } - - public class JuceWebChromeClient extends WebChromeClient - { - public JuceWebChromeClient (long hostToUse) - { - host = hostToUse; - } - - @Override - public void onCloseWindow (WebView window) - { - webViewCloseWindowRequest (host, window); - } - - @Override - public boolean onCreateWindow (WebView view, boolean isDialog, - boolean isUserGesture, Message resultMsg) - { - webViewCreateWindowRequest (host, view); - return false; - } - - private long host; - private final Object hostLock = new Object(); - } - - $$JuceAndroidCameraCode$$ // If you get an error here, you need to re-save your project with the Projucer! - $$JuceAndroidVideoCode$$ // If you get an error here, you need to re-save your project with the Projucer! - - //============================================================================== - public static final String getLocaleValue (boolean isRegion) - { - java.util.Locale locale = java.util.Locale.getDefault(); - - return isRegion ? locale.getCountry() - : locale.getLanguage(); - } - - private static final String getFileLocation (String type) - { - return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath(); - } - - public static final String getDocumentsFolder() - { - if (getAndroidSDKVersion() >= 19) - return getFileLocation ("Documents"); - - return Environment.getDataDirectory().getAbsolutePath(); - } - - public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); } - public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); } - public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); } - public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); } - - //============================================================================== - @Override - protected void onActivityResult (int requestCode, int resultCode, Intent data) - { - appActivityResult (requestCode, resultCode, data); - } - - @Override - protected void onNewIntent (Intent intent) - { - super.onNewIntent(intent); - setIntent(intent); - - appNewIntent (intent); - } - - //============================================================================== - public final Typeface getTypeFaceFromAsset (String assetName) - { - try - { - return Typeface.createFromAsset (this.getResources().getAssets(), assetName); - } - catch (Throwable e) {} - - return null; - } - - final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); - - public static String bytesToHex (byte[] bytes) - { - char[] hexChars = new char[bytes.length * 2]; - - for (int j = 0; j < bytes.length; ++j) - { - int v = bytes[j] & 0xff; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0f]; - } - - return new String (hexChars); - } - - final private java.util.Map dataCache = new java.util.HashMap(); - - synchronized private final File getDataCacheFile (byte[] data) - { - try - { - java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5"); - digest.update (data); - - String key = bytesToHex (digest.digest()); - - if (dataCache.containsKey (key)) - return (File) dataCache.get (key); - - File f = new File (this.getCacheDir(), "bindata_" + key); - f.delete(); - FileOutputStream os = new FileOutputStream (f); - os.write (data, 0, data.length); - dataCache.put (key, f); - return f; - } - catch (Throwable e) {} - - return null; - } - - private final void clearDataCache() - { - java.util.Iterator it = dataCache.values().iterator(); - - while (it.hasNext()) - { - File f = (File) it.next(); - f.delete(); - } - } - - public final Typeface getTypeFaceFromByteArray (byte[] data) - { - try - { - File f = getDataCacheFile (data); - - if (f != null) - return Typeface.createFromFile (f); - } - catch (Exception e) - { - Log.e ("JUCE", e.toString()); - } - - return null; - } - - public static final int getAndroidSDKVersion() - { - return android.os.Build.VERSION.SDK_INT; - } - - public final String audioManagerGetProperty (String property) - { - Object obj = getSystemService (AUDIO_SERVICE); - if (obj == null) - return null; - - java.lang.reflect.Method method; - - try - { - method = obj.getClass().getMethod ("getProperty", String.class); - } - catch (SecurityException e) { return null; } - catch (NoSuchMethodException e) { return null; } - - if (method == null) - return null; - - try - { - return (String) method.invoke (obj, property); - } - catch (java.lang.IllegalArgumentException e) {} - catch (java.lang.IllegalAccessException e) {} - catch (java.lang.reflect.InvocationTargetException e) {} - - return null; - } - - public final boolean hasSystemFeature (String property) - { - return getPackageManager().hasSystemFeature (property); - } -} diff --git a/modules/juce_core/native/java/JuceFirebaseInstanceIdService.java b/modules/juce_core/native/java/JuceFirebaseInstanceIdService.java deleted file mode 100644 index 1c9017bd9b..0000000000 --- a/modules/juce_core/native/java/JuceFirebaseInstanceIdService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.juce; - -import com.google.firebase.iid.*; - -public final class JuceFirebaseInstanceIdService extends FirebaseInstanceIdService -{ - private native void firebaseInstanceIdTokenRefreshed (String token); - - @Override - public void onTokenRefresh() - { - String token = FirebaseInstanceId.getInstance().getToken(); - - firebaseInstanceIdTokenRefreshed (token); - } -} diff --git a/modules/juce_core/native/java/JuceFirebaseMessagingService.java b/modules/juce_core/native/java/JuceFirebaseMessagingService.java deleted file mode 100644 index 28e9e9f3e5..0000000000 --- a/modules/juce_core/native/java/JuceFirebaseMessagingService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.juce; - -import com.google.firebase.messaging.*; - -public final class JuceFirebaseMessagingService extends FirebaseMessagingService -{ - private native void firebaseRemoteMessageReceived (RemoteMessage message); - private native void firebaseRemoteMessagesDeleted(); - private native void firebaseRemoteMessageSent (String messageId); - private native void firebaseRemoteMessageSendError (String messageId, String error); - - @Override - public void onMessageReceived (RemoteMessage message) - { - firebaseRemoteMessageReceived (message); - } - - @Override - public void onDeletedMessages() - { - firebaseRemoteMessagesDeleted(); - } - - @Override - public void onMessageSent (String messageId) - { - firebaseRemoteMessageSent (messageId); - } - - @Override - public void onSendError (String messageId, Exception e) - { - firebaseRemoteMessageSendError (messageId, e.toString()); - } -} diff --git a/modules/juce_core/native/java/README.txt b/modules/juce_core/native/java/README.txt new file mode 100644 index 0000000000..88be61c18a --- /dev/null +++ b/modules/juce_core/native/java/README.txt @@ -0,0 +1,19 @@ +The java code in the module's native/java subfolders were used to generate dex byte-code in various places in the JUCE framework. These are the steps required to re-generate the dex byte-code from any java source code inside the native/java subfolders: + +1. Create a new JUCE android project with the minimal sdk version which is required for the java source code you wish to compile. +2. Move the the .java files that you wish to create source code for, into the module's native/javacore/app folder. Remember that .java files need to be in nested sub-folders which resemble their package, i.e. a Java class com.roli.juce.HelloWorld.java should be in the module's native/javacore/app/com/roli/juce folder. +3. Build your project with AS and run. The app will now use the source code in the folder creates in step 2 - so you can debug your java code this way. +4. Once everything is working, rebuild your app in release mode. +5. Go to your app's Builds/Android folder. Inside there you will find app/build/intermediates/classes/release_/release. Inside of that folder, you will find all your java byte-code compiled classes. Remove any classes that you are not interested in (typically you'll find Java.class, JuceApp.class and JuceSharingContentProvider.class which you will probably want to remove). +6. Inside of app/build/intermediates/classes/release_/release execute the following dx command: + + /build-tools//dx --dex --verbose --min-sdk-version= --output /tmp/JavaDexByteCode.dex . + + + Replace with the minimal sdk version you used in step 1. + +7. gzip the output: + +gzip /tmp/JavaDexByteCode.dex + +8. The output /tmp/JavaDexByteCode.dex.gz is now the byte code that can be included into JUCE. You can use the Projucer's BinaryData generator functionality to get this into a convenient char array like form. diff --git a/modules/juce_core/native/java/com/roli/juce/FragmentOverlay.java b/modules/juce_core/native/java/com/roli/juce/FragmentOverlay.java new file mode 100644 index 0000000000..0e96761949 --- /dev/null +++ b/modules/juce_core/native/java/com/roli/juce/FragmentOverlay.java @@ -0,0 +1,58 @@ +package com.roli.juce; + +import android.app.DialogFragment; +import android.content.Intent; +import android.os.Bundle; + +public class FragmentOverlay extends DialogFragment +{ + @Override + public void onCreate (Bundle state) + { + super.onCreate (state); + cppThis = getArguments ().getLong ("cppThis"); + + if (cppThis != 0) + onCreateNative (cppThis, state); + } + + @Override + public void onStart () + { + super.onStart (); + + if (cppThis != 0) + onStartNative (cppThis); + } + + public void onRequestPermissionsResult (int requestCode, + String[] permissions, + int[] grantResults) + { + if (cppThis != 0) + onRequestPermissionsResultNative (cppThis, requestCode, + permissions, grantResults); + } + + @Override + public void onActivityResult (int requestCode, int resultCode, Intent data) + { + if (cppThis != 0) + onActivityResultNative (cppThis, requestCode, resultCode, data); + } + + public void close () + { + cppThis = 0; + dismiss (); + } + + //============================================================================== + private long cppThis = 0; + + private native void onActivityResultNative (long myself, int requestCode, int resultCode, Intent data); + private native void onCreateNative (long myself, Bundle state); + private native void onStartNative (long myself); + private native void onRequestPermissionsResultNative (long myself, int requestCode, + String[] permissions, int[] grantResults); +} diff --git a/modules/juce_core/native/java/com/roli/juce/JuceHTTPStream.java b/modules/juce_core/native/java/com/roli/juce/JuceHTTPStream.java new file mode 100644 index 0000000000..ee4302b5d1 --- /dev/null +++ b/modules/juce_core/native/java/com/roli/juce/JuceHTTPStream.java @@ -0,0 +1,407 @@ +package com.roli.juce; + +import java.lang.Runnable; +import java.io.*; +import java.net.URL; +import java.net.HttpURLConnection; +import java.util.concurrent.CancellationException; +import java.util.concurrent.Future; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Callable; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.atomic.*; + +public class JuceHTTPStream +{ + public JuceHTTPStream(String address, boolean isPostToUse, byte[] postDataToUse, + String headersToUse, int timeOutMsToUse, + int[] statusCodeToUse, StringBuffer responseHeadersToUse, + int numRedirectsToFollowToUse, String httpRequestCmdToUse) throws IOException + { + isPost = isPostToUse; + postData = postDataToUse; + headers = headersToUse; + timeOutMs = timeOutMsToUse; + statusCode = statusCodeToUse; + responseHeaders = responseHeadersToUse; + totalLength = -1; + numRedirectsToFollow = numRedirectsToFollowToUse; + httpRequestCmd = httpRequestCmdToUse; + + connection = createConnection(address, isPost, postData, headers, timeOutMs, httpRequestCmd); + } + + public static final JuceHTTPStream createHTTPStream(String address, boolean isPost, byte[] postData, + String headers, int timeOutMs, int[] statusCode, + StringBuffer responseHeaders, int numRedirectsToFollow, + String httpRequestCmd) + { + // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL) + if (timeOutMs < 0) + timeOutMs = 0; + else if (timeOutMs == 0) + timeOutMs = 30000; + + for (; ; ) + { + try + { + JuceHTTPStream httpStream = new JuceHTTPStream(address, isPost, postData, headers, + timeOutMs, statusCode, responseHeaders, + numRedirectsToFollow, httpRequestCmd); + + return httpStream; + } catch (Throwable e) + { + } + + return null; + } + } + + private final HttpURLConnection createConnection(String address, boolean isPost, byte[] postData, + String headers, int timeOutMs, String httpRequestCmdToUse) throws IOException + { + HttpURLConnection newConnection = (HttpURLConnection) (new URL(address).openConnection()); + + try + { + newConnection.setInstanceFollowRedirects(false); + newConnection.setConnectTimeout(timeOutMs); + newConnection.setReadTimeout(timeOutMs); + + // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines. + // So convert headers string to an array, with an element for each line + String headerLines[] = headers.split("\\n"); + + // Set request headers + for (int i = 0; i < headerLines.length; ++i) + { + int pos = headerLines[i].indexOf(":"); + + if (pos > 0 && pos < headerLines[i].length()) + { + String field = headerLines[i].substring(0, pos); + String value = headerLines[i].substring(pos + 1); + + if (value.length() > 0) + newConnection.setRequestProperty(field, value); + } + } + + newConnection.setRequestMethod(httpRequestCmd); + + if (isPost) + { + newConnection.setDoOutput(true); + + if (postData != null) + { + OutputStream out = newConnection.getOutputStream(); + out.write(postData); + out.flush(); + } + } + + return newConnection; + } catch (Throwable e) + { + newConnection.disconnect(); + throw new IOException("Connection error"); + } + } + + private final InputStream getCancellableStream(final boolean isInput) throws ExecutionException + { + synchronized (createFutureLock) + { + if (hasBeenCancelled.get()) + return null; + + streamFuture = executor.submit(new Callable() + { + @Override + public BufferedInputStream call() throws IOException + { + return new BufferedInputStream(isInput ? connection.getInputStream() + : connection.getErrorStream()); + } + }); + } + + try + { + return streamFuture.get(); + } catch (InterruptedException e) + { + return null; + } catch (CancellationException e) + { + return null; + } + } + + public final boolean connect() + { + boolean result = false; + int numFollowedRedirects = 0; + + while (true) + { + result = doConnect(); + + if (!result) + return false; + + if (++numFollowedRedirects > numRedirectsToFollow) + break; + + int status = statusCode[0]; + + if (status == 301 || status == 302 || status == 303 || status == 307) + { + // Assumes only one occurrence of "Location" + int pos1 = responseHeaders.indexOf("Location:") + 10; + int pos2 = responseHeaders.indexOf("\n", pos1); + + if (pos2 > pos1) + { + String currentLocation = connection.getURL().toString(); + String newLocation = responseHeaders.substring(pos1, pos2); + + try + { + // Handle newLocation whether it's absolute or relative + URL baseUrl = new URL(currentLocation); + URL newUrl = new URL(baseUrl, newLocation); + String transformedNewLocation = newUrl.toString(); + + if (transformedNewLocation != currentLocation) + { + // Clear responseHeaders before next iteration + responseHeaders.delete(0, responseHeaders.length()); + + synchronized (createStreamLock) + { + if (hasBeenCancelled.get()) + return false; + + connection.disconnect(); + + try + { + connection = createConnection(transformedNewLocation, isPost, + postData, headers, timeOutMs, + httpRequestCmd); + } catch (Throwable e) + { + return false; + } + } + } else + { + break; + } + } catch (Throwable e) + { + return false; + } + } else + { + break; + } + } else + { + break; + } + } + + return result; + } + + private final boolean doConnect() + { + synchronized (createStreamLock) + { + if (hasBeenCancelled.get()) + return false; + + try + { + try + { + inputStream = getCancellableStream(true); + } catch (ExecutionException e) + { + if (connection.getResponseCode() < 400) + { + statusCode[0] = connection.getResponseCode(); + connection.disconnect(); + return false; + } + } finally + { + statusCode[0] = connection.getResponseCode(); + } + + try + { + if (statusCode[0] >= 400) + inputStream = getCancellableStream(false); + else + inputStream = getCancellableStream(true); + } catch (ExecutionException e) + { + } + + for (java.util.Map.Entry> entry : connection.getHeaderFields().entrySet()) + { + if (entry.getKey() != null && entry.getValue() != null) + { + responseHeaders.append(entry.getKey() + ": " + + android.text.TextUtils.join(",", entry.getValue()) + "\n"); + + if (entry.getKey().compareTo("Content-Length") == 0) + totalLength = Integer.decode(entry.getValue().get(0)); + } + } + + return true; + } catch (IOException e) + { + return false; + } + } + } + + static class DisconnectionRunnable implements Runnable + { + public DisconnectionRunnable(HttpURLConnection theConnection, + InputStream theInputStream, + ReentrantLock theCreateStreamLock, + Object theCreateFutureLock, + Future theStreamFuture) + { + connectionToDisconnect = theConnection; + inputStream = theInputStream; + createStreamLock = theCreateStreamLock; + createFutureLock = theCreateFutureLock; + streamFuture = theStreamFuture; + } + + public void run() + { + try + { + if (!createStreamLock.tryLock()) + { + synchronized (createFutureLock) + { + if (streamFuture != null) + streamFuture.cancel(true); + } + + createStreamLock.lock(); + } + + if (connectionToDisconnect != null) + connectionToDisconnect.disconnect(); + + if (inputStream != null) + inputStream.close(); + } catch (IOException e) + { + } finally + { + createStreamLock.unlock(); + } + } + + private HttpURLConnection connectionToDisconnect; + private InputStream inputStream; + private ReentrantLock createStreamLock; + private Object createFutureLock; + Future streamFuture; + } + + public final void release() + { + DisconnectionRunnable disconnectionRunnable = new DisconnectionRunnable(connection, + inputStream, + createStreamLock, + createFutureLock, + streamFuture); + + synchronized (createStreamLock) + { + hasBeenCancelled.set(true); + + connection = null; + } + + Thread disconnectionThread = new Thread(disconnectionRunnable); + disconnectionThread.start(); + } + + public final int read(byte[] buffer, int numBytes) + { + int num = 0; + + try + { + synchronized (createStreamLock) + { + if (inputStream != null) + num = inputStream.read(buffer, 0, numBytes); + } + } catch (IOException e) + { + } + + if (num > 0) + position += num; + + return num; + } + + public final long getPosition() + { + return position; + } + + public final long getTotalLength() + { + return totalLength; + } + + public final boolean isExhausted() + { + return false; + } + + public final boolean setPosition(long newPos) + { + return false; + } + + private boolean isPost; + private byte[] postData; + private String headers; + private int timeOutMs; + String httpRequestCmd; + private HttpURLConnection connection; + private int[] statusCode; + private StringBuffer responseHeaders; + private int totalLength; + private int numRedirectsToFollow; + private InputStream inputStream; + private long position; + private final ReentrantLock createStreamLock = new ReentrantLock(); + private final Object createFutureLock = new Object(); + private AtomicBoolean hasBeenCancelled = new AtomicBoolean(); + + private final ExecutorService executor = Executors.newCachedThreadPool(Executors.defaultThreadFactory()); + Future streamFuture; +} \ No newline at end of file diff --git a/modules/juce_core/native/javacore/app/com/roli/juce/JuceApp.java b/modules/juce_core/native/javacore/app/com/roli/juce/JuceApp.java new file mode 100644 index 0000000000..bad3737895 --- /dev/null +++ b/modules/juce_core/native/javacore/app/com/roli/juce/JuceApp.java @@ -0,0 +1,15 @@ +package com.roli.juce; + +import com.roli.juce.Java; + +import android.app.Application; + +public class JuceApp extends Application +{ + @Override + public void onCreate () + { + super.onCreate (); + Java.initialiseJUCE (this); + } +} diff --git a/modules/juce_core/native/javacore/init/com/roli/juce/Java.java b/modules/juce_core/native/javacore/init/com/roli/juce/Java.java new file mode 100644 index 0000000000..a5622974bd --- /dev/null +++ b/modules/juce_core/native/javacore/init/com/roli/juce/Java.java @@ -0,0 +1,13 @@ +package com.roli.juce; + +import android.content.Context; + +public class Java +{ + static + { + System.loadLibrary ("juce_jni"); + } + + public native static void initialiseJUCE (Context appContext); +} \ No newline at end of file diff --git a/modules/juce_core/native/juce_android_Files.cpp b/modules/juce_core/native/juce_android_Files.cpp index 180935429a..65f76164ad 100644 --- a/modules/juce_core/native/juce_android_Files.cpp +++ b/modules/juce_core/native/juce_android_Files.cpp @@ -23,7 +23,7 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Landroid/content/Context;Landroid/media/MediaScannerConnection$MediaScannerConnectionClient;)V") \ METHOD (connect, "connect", "()V") \ METHOD (disconnect, "disconnect", "()V") \ @@ -32,7 +32,7 @@ namespace juce DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (query, "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;") \ METHOD (openInputStream, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;") \ METHOD (openOutputStream, "openOutputStream", "(Landroid/net/Uri;)Ljava/io/OutputStream;") @@ -40,7 +40,7 @@ DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (moveToFirst, "moveToFirst", "()Z") \ METHOD (getColumnIndex, "getColumnIndex", "(Ljava/lang/String;)I") \ METHOD (getString, "getString", "(I)Ljava/lang/String;") \ @@ -49,14 +49,15 @@ DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver") DECLARE_JNI_CLASS (AndroidCursor, "android/database/Cursor") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getExternalStorageDirectory, "getExternalStorageDirectory", "()Ljava/io/File;") \ STATICMETHOD (getExternalStoragePublicDirectory, "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;") \ + STATICMETHOD (getDataDirectory, "getDataDirectory", "()Ljava/io/File;") DECLARE_JNI_CLASS (AndroidEnvironment, "android/os/Environment") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (close, "close", "()V") \ METHOD (flush, "flush", "()V") \ METHOD (write, "write", "([BII)V") @@ -64,6 +65,55 @@ DECLARE_JNI_CLASS (AndroidEnvironment, "android/os/Environment") DECLARE_JNI_CLASS (AndroidOutputStream, "java/io/OutputStream") #undef JNI_CLASS_MEMBERS +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + FIELD (publicSourceDir, "publicSourceDir", "Ljava/lang/String;") \ + FIELD (dataDir, "dataDir", "Ljava/lang/String;") + +DECLARE_JNI_CLASS (AndroidApplicationInfo, "android/content/pm/ApplicationInfo") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +static File juceFile (LocalRef obj) +{ + auto* env = getEnv(); + + if (env->IsInstanceOf (obj.get(), JavaFile) != 0) + return File (juceString (LocalRef ((jstring) env->CallObjectMethod (obj.get(), + JavaFile.getAbsolutePath)))); + + return {}; +} + +static File getWellKnownFolder (const char* folderId) +{ + auto* env = getEnv(); + auto fieldId = env->GetStaticFieldID (AndroidEnvironment, folderId, "Ljava/lang/String;"); + + if (fieldId == 0) + { + // unknown field in environment + jassertfalse; + return {}; + } + + LocalRef fieldValue (env->GetStaticObjectField (AndroidEnvironment, fieldId)); + + if (fieldValue == nullptr) + return {}; + + LocalRef downloadFolder (env->CallStaticObjectMethod (AndroidEnvironment, + AndroidEnvironment.getExternalStoragePublicDirectory, + fieldValue.get())); + + return (downloadFolder ? juceFile (downloadFolder) : File()); +} + +static LocalRef urlToUri (const URL& url) +{ + return LocalRef (getEnv()->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, + javaString (url.toString (true)).get())); +} + //============================================================================== struct AndroidContentUriResolver { @@ -74,7 +124,7 @@ public: jassert (url.getScheme() == "content"); auto* env = getEnv(); - LocalRef contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver)); + LocalRef contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); if (contentResolver) return LocalRef ((env->CallObjectMethod (contentResolver.get(), @@ -116,7 +166,7 @@ public: else if (type.equalsIgnoreCase ("downloads")) { auto subDownloadPath = url.getSubPath().fromFirstOccurrenceOf ("tree/downloads", false, false); - return File (getWellKnownFolder ("Download").getFullPathName() + "/" + subDownloadPath); + return File (getWellKnownFolder ("DIRECTORY_DOWNLOADS").getFullPathName() + "/" + subDownloadPath); } else { @@ -142,7 +192,7 @@ public: { auto uri = urlToUri (url); auto* env = getEnv(); - LocalRef contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver)); + LocalRef contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); if (contentResolver == 0) return {}; @@ -166,7 +216,7 @@ private: { auto uri = urlToUri (url); auto* env = getEnv(); - LocalRef contentResolver (android.activity.callObjectMethod (JuceAppActivity.getContentResolver)); + LocalRef contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); if (contentResolver) { @@ -217,17 +267,6 @@ private: return {}; } - //============================================================================== - static File getWellKnownFolder (const String& folderId) - { - auto* env = getEnv(); - LocalRef downloadFolder (env->CallStaticObjectMethod (AndroidEnvironment, - AndroidEnvironment.getExternalStoragePublicDirectory, - javaString (folderId).get())); - - return (downloadFolder ? juceFile (downloadFolder) : File()); - } - //============================================================================== static File getStorageDevicePath (const String& storageId) { @@ -254,15 +293,15 @@ private: { Array results; - if (getSDKVersion() >= 19) + if (getAndroidSDKVersion() >= 19) { auto* env = getEnv(); - static jmethodID m = (env->GetMethodID (JuceAppActivity, "getExternalFilesDirs", + static jmethodID m = (env->GetMethodID (AndroidContext, "getExternalFilesDirs", "(Ljava/lang/String;)[Ljava/io/File;")); if (m == 0) return {}; - auto paths = convertFileArray (LocalRef (android.activity.callObjectMethod (m, nullptr))); + auto paths = convertFileArray (LocalRef (env->CallObjectMethod (getAppContext().get(), m, nullptr))); for (auto path : paths) results.add (getMountPointForFile (path)); @@ -348,33 +387,6 @@ private: return files; } - static File juceFile (LocalRef obj) - { - auto* env = getEnv(); - - if (env->IsInstanceOf (obj.get(), JavaFile) != 0) - return File (juceString (LocalRef ((jstring) env->CallObjectMethod (obj.get(), - JavaFile.getAbsolutePath)))); - - return {}; - } - - //============================================================================== - static int getSDKVersion() - { - static int sdkVersion - = getEnv()->CallStaticIntMethod (JuceAppActivity, - JuceAppActivity.getAndroidSDKVersion); - - return sdkVersion; - } - - static LocalRef urlToUri (const URL& url) - { - return LocalRef (getEnv()->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, - javaString (url.toString (true)).get())); - } - //============================================================================== static String getStringUsingDataColumn (const String& columnNameToUse, JNIEnv* env, const LocalRef& uri, @@ -525,9 +537,24 @@ String File::getVersion() const return {}; } -static File getSpecialFile (jmethodID type) +static File getDocumentsDirectory() { - return File (juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity, type)))); + auto* env = getEnv(); + + if (getAndroidSDKVersion() >= 19) + return getWellKnownFolder ("DIRECTORY_DOCUMENTS"); + + return juceFile (LocalRef (env->CallStaticObjectMethod (AndroidEnvironment, AndroidEnvironment.getDataDirectory))); +} + +static File getAppDataDir (bool dataDir) +{ + auto* env = getEnv(); + + LocalRef applicationInfo (env->CallObjectMethod (getAppContext().get(), AndroidContext.getApplicationInfo)); + LocalRef jString (env->GetObjectField (applicationInfo.get(), dataDir ? AndroidApplicationInfo.dataDir : AndroidApplicationInfo.publicSourceDir)); + + return {juceString ((jstring) jString.get())}; } File File::getSpecialLocation (const SpecialLocationType type) @@ -538,20 +565,41 @@ File File::getSpecialLocation (const SpecialLocationType type) case userApplicationDataDirectory: case userDesktopDirectory: case commonApplicationDataDirectory: - return File (android.appDataDir); + { + static File appDataDir = getAppDataDir (true); + return appDataDir; + } case userDocumentsDirectory: - case commonDocumentsDirectory: return getSpecialFile (JuceAppActivity.getDocumentsFolder); - case userPicturesDirectory: return getSpecialFile (JuceAppActivity.getPicturesFolder); - case userMusicDirectory: return getSpecialFile (JuceAppActivity.getMusicFolder); - case userMoviesDirectory: return getSpecialFile (JuceAppActivity.getMoviesFolder); + case commonDocumentsDirectory: + { + static auto docsDir = getDocumentsDirectory(); + return docsDir; + } + + case userPicturesDirectory: + { + static auto picturesDir = getWellKnownFolder ("DIRECTORY_PICTURES"); + return picturesDir; + } + + case userMusicDirectory: + { + static auto musicDir = getWellKnownFolder ("DIRECTORY_MUSIC"); + return musicDir; + } + case userMoviesDirectory: + { + static auto moviesDir = getWellKnownFolder ("DIRECTORY_MOVIES"); + return moviesDir; + } case globalApplicationsDirectory: return File ("/system/app"); case tempDirectory: { - File tmp = File (android.appDataDir).getChildFile (".temp"); + File tmp = getSpecialLocation (commonApplicationDataDirectory).getChildFile (".temp"); tmp.createDirectory(); return File (tmp.getFullPathName()); } @@ -560,7 +608,7 @@ File File::getSpecialLocation (const SpecialLocationType type) case currentExecutableFile: case currentApplicationFile: case hostApplicationPath: - return juce_getExecutableFile(); + return getAppDataDir (false); default: jassertfalse; // unknown type? @@ -581,8 +629,13 @@ bool File::moveToTrash() const JUCE_API bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String&) { - const LocalRef t (javaString (fileName)); - android.activity.callVoidMethod (JuceAppActivity.launchURL, t.get()); + URL targetURL (File {fileName}); + auto* env = getEnv(); + + const LocalRef action (javaString ("android.intent.action.VIEW")); + LocalRef intent (env->NewObject (AndroidIntent, AndroidIntent.constructWithUri, action.get(), urlToUri (targetURL).get())); + + env->CallVoidMethod (getCurrentActivity(), AndroidContext.startActivity, intent.get()); return true; } @@ -595,10 +648,10 @@ class SingleMediaScanner : public MediaScannerConnectionClient { public: SingleMediaScanner (const String& filename) - : msc (getEnv()->NewObject (MediaScannerConnection, - MediaScannerConnection.constructor, - android.activity.get(), - CreateJavaInterface (this, "android/media/MediaScannerConnection$MediaScannerConnectionClient").get())), + : msc (LocalRef (getEnv()->NewObject (MediaScannerConnection, + MediaScannerConnection.constructor, + getAppContext().get(), + CreateJavaInterface (this, "android/media/MediaScannerConnection$MediaScannerConnectionClient").get()))), file (filename) { getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.connect); diff --git a/modules/juce_core/native/juce_android_JNIHelpers.cpp b/modules/juce_core/native/juce_android_JNIHelpers.cpp new file mode 100644 index 0000000000..cda9a67379 --- /dev/null +++ b/modules/juce_core/native/juce_android_JNIHelpers.cpp @@ -0,0 +1,687 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== + */ + +namespace juce +{ + +//============================================================================== +static const uint8 invocationHandleByteCode[] = { +31,139,8,8,170,94,229,91,0,3,105,110,118,111,99,97,116,105,111,110,95,104,97,110,100,108,101,114,46,100,101,120,0,109, +148,75,107,19,81,20,199,207,189,119,38,169,181,109,98,218,138,72,23,81,20,68,193,169,218,130,144,42,130,162,53,140,15, +104,200,66,187,232,52,185,181,147,78,103,98,58,141,33,184,168,197,47,224,66,168,187,186,241,83,168,184,16,247,162,31,193, +69,151,174,220,40,234,255,62,154,4,237,192,239,62,206,57,115,30,115,239,153,186,236,12,79,95,154,165,159,187,91,95,227, +217,197,247,87,42,31,231,191,156,157,58,241,253,199,220,201,79,75,175,118,58,14,81,147,136,58,213,153,2,217,231,131,32, +154,32,35,63,164,246,0,102,244,13,48,48,129,33,139,121,138,153,125,5,131,131,119,82,204,203,156,168,1,214,65,10,186,224, +53,120,7,62,131,61,144,131,237,57,112,30,92,4,183,192,93,176,4,154,160,11,182,193,11,97,252,171,216,46,200,144,137,59, +100,243,26,6,35,0,46,9,166,116,131,155,89,113,159,27,125,214,214,116,216,174,23,185,241,89,208,181,8,173,99,240,48,106, +247,99,182,198,156,149,231,245,204,232,136,246,203,173,189,65,189,61,7,209,31,60,167,48,239,26,119,90,183,35,84,190,66, +175,201,230,218,204,43,201,81,172,30,76,131,25,66,180,190,133,169,81,107,139,164,243,112,123,25,154,253,194,53,232,17, +231,2,74,190,140,106,212,190,57,205,201,97,99,168,207,209,223,71,61,147,202,182,57,104,59,74,11,45,212,255,56,251,60,251, +50,251,166,157,81,81,71,80,83,129,206,252,238,133,239,101,162,242,72,237,217,186,246,251,171,106,51,248,242,162,183,218, +183,207,204,133,113,152,94,37,86,38,215,47,251,190,79,142,175,198,211,126,45,89,247,90,73,20,122,141,205,154,244,202,24, +110,199,237,164,22,164,97,18,207,7,113,61,146,173,18,29,247,235,65,212,14,215,188,32,142,147,84,235,188,202,106,43,121, +178,81,162,130,223,8,218,129,23,5,241,35,239,222,114,67,214,210,18,77,14,200,180,93,176,28,201,18,162,245,197,45,185,18, +193,214,59,48,218,255,102,119,100,186,154,212,75,196,170,196,171,101,26,127,120,64,84,183,22,201,160,69,249,122,184,209, +12,210,218,234,205,48,14,162,176,43,105,108,95,162,130,173,73,26,90,217,215,100,66,35,25,141,145,66,91,94,79,226,84,118, +82,114,219,65,180,41,137,115,54,62,197,142,57,196,132,186,86,207,182,156,95,156,111,115,98,10,182,43,4,123,43,24,219,19, +185,127,206,70,247,159,237,77,62,208,159,98,160,71,157,129,62,117,169,223,171,25,234,247,171,200,155,181,62,231,162,121, +231,169,178,41,26,185,186,207,44,111,228,234,142,243,162,137,171,250,219,41,246,239,56,217,181,190,251,214,167,250,127, +252,5,76,239,47,69,120,4,0,0}; + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + STATICMETHOD (newProxyInstance, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;") \ + + DECLARE_JNI_CLASS (JavaProxy, "java/lang/reflect/Proxy") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + METHOD (clear, "clear", "()V") \ + CALLBACK (juce_invokeImplementer, "dispatchInvoke", "(JLjava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;") \ + CALLBACK (juce_dispatchDelete, "dispatchFinalize", "(J)V") + + DECLARE_JNI_CLASS_WITH_BYTECODE (JuceInvocationHandler, "com/roli/juce/JuceInvocationHandler", 10, invocationHandleByteCode, sizeof (invocationHandleByteCode)) +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (findClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;") \ + STATICMETHOD (getSystemClassLoader, "getSystemClassLoader", "()Ljava/lang/ClassLoader;") + +DECLARE_JNI_CLASS (JavaClassLoader, "java/lang/ClassLoader") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V") + +DECLARE_JNI_CLASS (AndroidDexClassLoader, "dalvik/system/DexClassLoader") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V") + +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidInMemoryDexClassLoader, "dalvik/system/InMemoryDexClassLoader", 26) +#undef JNI_CLASS_MEMBERS + +//============================================================================== +struct SystemJavaClassComparator +{ + static int compareElements (JNIClassBase* first, JNIClassBase* second) + { + auto isSysClassA = isSystemClass (first); + auto isSysClassB = isSystemClass (second); + + if ((! isSysClassA) && (! isSysClassB)) + { + return DefaultElementComparator::compareElements (first != nullptr ? first->byteCode != nullptr : false, + second != nullptr ? second->byteCode != nullptr : false); + } + + return DefaultElementComparator::compareElements (isSystemClass (first), + isSystemClass (second)); + } + + static bool isSystemClass (JNIClassBase* cls) + { + if (cls == nullptr) + return false; + + String path (cls->getClassPath()); + + return path.startsWith ("java/") + || path.startsWith ("android/") + || path.startsWith ("dalvik/"); + } +}; + +//============================================================================== +JNIClassBase::JNIClassBase (const char* cp, int classMinSDK, const void* bc, size_t n) + : classPath (cp), byteCode (bc), byteCodeSize (n), minSDK (classMinSDK), classRef (0) +{ + SystemJavaClassComparator comparator; + + getClasses().addSorted (comparator, this); +} + +JNIClassBase::~JNIClassBase() +{ + getClasses().removeFirstMatchingValue (this); +} + +Array& JNIClassBase::getClasses() +{ + static Array classes; + return classes; +} + +// Get code cache directory without yet having a context object +static File getCodeCacheDirectory() +{ + int pid = getpid(); + File cmdline("/proc/" + String(pid) + "/cmdline"); + + auto bundleId = cmdline.loadFileAsString().trimStart().trimEnd(); + + if (bundleId.isEmpty()) + return {}; + + return File("/data/data/" + bundleId + "/code_cache"); +} + +void JNIClassBase::initialise (JNIEnv* env) +{ + auto sdkVersion = getAndroidSDKVersion(); + + if (sdkVersion >= minSDK) + { + LocalRef classNameAndPackage (javaString (String (classPath).replaceCharacter (L'/', L'.'))); + static Array byteCodeLoaders; + + if (! SystemJavaClassComparator::isSystemClass(this)) + { + LocalRef defaultClassLoader (env->CallStaticObjectMethod (JavaClassLoader, JavaClassLoader.getSystemClassLoader)); + tryLoadingClassWithClassLoader (env, defaultClassLoader.get()); + + if (classRef == 0) + { + for (auto& byteCodeLoader : byteCodeLoaders) + { + tryLoadingClassWithClassLoader (env, byteCodeLoader.get()); + + if (classRef != 0) + break; + } + + // fallback by trying to load the class from bytecode + if (byteCode != nullptr) + { + LocalRef byteCodeClassLoader; + + MemoryOutputStream uncompressedByteCode; + + { + MemoryInputStream rawGZipData (byteCode, byteCodeSize, false); + GZIPDecompressorInputStream gzipStream (&rawGZipData, false, GZIPDecompressorInputStream::gzipFormat); + uncompressedByteCode.writeFromInputStream (gzipStream, -1); + } + + if (sdkVersion >= 26) + { + LocalRef byteArray (env->NewByteArray ((jsize) uncompressedByteCode.getDataSize())); + jboolean isCopy; + auto* dst = env->GetByteArrayElements (byteArray.get(), &isCopy); + memcpy (dst, uncompressedByteCode.getData(), uncompressedByteCode.getDataSize()); + env->ReleaseByteArrayElements (byteArray.get(), dst, 0); + + LocalRef byteBuffer (env->CallStaticObjectMethod (JavaByteBuffer, JavaByteBuffer.wrap, byteArray.get())); + + byteCodeClassLoader = LocalRef (env->NewObject (AndroidInMemoryDexClassLoader, + AndroidInMemoryDexClassLoader.constructor, + byteBuffer.get(), defaultClassLoader.get())); + } + else if (uncompressedByteCode.getDataSize() >= 32) + { + auto codeCacheDir = getCodeCacheDirectory(); + + // The dex file has an embedded 20-byte long SHA-1 signature at offset 12 + auto fileName = String::toHexString ((char*)uncompressedByteCode.getData() + 12, 20, 0) + ".dex"; + auto dexFile = codeCacheDir.getChildFile (fileName); + auto optimizedDirectory = codeCacheDir.getChildFile ("optimized_cache"); + optimizedDirectory.createDirectory(); + + if (dexFile.replaceWithData (uncompressedByteCode.getData(), uncompressedByteCode.getDataSize())) + { + byteCodeClassLoader = LocalRef (env->NewObject (AndroidDexClassLoader, + AndroidDexClassLoader.constructor, + javaString (dexFile.getFullPathName()).get(), + javaString (optimizedDirectory.getFullPathName()).get(), + nullptr, + defaultClassLoader.get())); + } + else + { + // can't write to cache folder + jassertfalse; + } + } + + if (byteCodeClassLoader != nullptr) + { + tryLoadingClassWithClassLoader (env, byteCodeClassLoader.get()); + byteCodeLoaders.add (GlobalRef(byteCodeClassLoader)); + } + } + } + } + + if (classRef == 0) + classRef = (jclass) env->NewGlobalRef (LocalRef (env->FindClass (classPath))); + + jassert (classRef != 0); + initialiseFields (env); + } +} + +void JNIClassBase::tryLoadingClassWithClassLoader (JNIEnv* env, jobject classLoader) +{ + LocalRef classNameAndPackage (javaString (String (classPath).replaceCharacter (L'/', L'.'))); + + // Android SDK <= 19 has a bug where the class loader might throw an exception but still return + // a non-nullptr. So don't assign the result of this call to a jobject just yet... + auto classObj = env->CallObjectMethod (classLoader, JavaClassLoader.findClass, classNameAndPackage.get()); + + if (jthrowable exception = env->ExceptionOccurred ()) + { + env->ExceptionClear(); + classObj = 0; + } + + // later versions of Android don't throw at all, so re-check the object + if (classObj != nullptr) + classRef = (jclass) env->NewGlobalRef (LocalRef (classObj)); +} + +void JNIClassBase::release (JNIEnv* env) +{ + if (classRef != 0) + env->DeleteGlobalRef (classRef); +} + +void JNIClassBase::initialiseAllClasses (JNIEnv* env) +{ + const Array& classes = getClasses(); + for (int i = classes.size(); --i >= 0;) + classes.getUnchecked(i)->initialise (env); +} + +void JNIClassBase::releaseAllClasses (JNIEnv* env) +{ + const Array& classes = getClasses(); + for (int i = classes.size(); --i >= 0;) + classes.getUnchecked(i)->release (env); +} + +jmethodID JNIClassBase::resolveMethod (JNIEnv* env, const char* methodName, const char* params) +{ + jmethodID m = env->GetMethodID (classRef, methodName, params); + jassert (m != 0); + return m; +} + +jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params) +{ + jmethodID m = env->GetStaticMethodID (classRef, methodName, params); + jassert (m != 0); + return m; +} + +jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetFieldID (classRef, fieldName, signature); + jassert (f != 0); + return f; +} + +jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature) +{ + jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature); + jassert (f != 0); + return f; +} + +void JNIClassBase::resolveCallbacks (JNIEnv* env, const Array& nativeCallbacks) +{ + if (nativeCallbacks.size() > 0) + env->RegisterNatives (classRef, nativeCallbacks.begin(), (jint) nativeCallbacks.size()); +} + +//============================================================================== +LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, + const StringArray& interfaceNames, + LocalRef subclass) +{ + auto* env = getEnv(); + + implementer->javaSubClass = GlobalRef (subclass); + + // you need to override at least one interface + jassert (interfaceNames.size() > 0); + + auto classArray = LocalRef (env->NewObjectArray (interfaceNames.size(), JavaClass, nullptr)); + LocalRef classLoader; + + for (auto i = 0; i < interfaceNames.size(); ++i) + { + auto aClass = LocalRef (env->FindClass (interfaceNames[i].toRawUTF8())); + + if (aClass != nullptr) + { + if (i == 0) + classLoader = LocalRef (env->CallObjectMethod (aClass, JavaClass.getClassLoader)); + + env->SetObjectArrayElement ((jobjectArray) classArray.get(), i, aClass); + } + else + { + // interface class not found + jassertfalse; + } + } + + auto invocationHandler = LocalRef (env->NewObject (JuceInvocationHandler, JuceInvocationHandler.constructor, + reinterpret_cast (implementer))); + + // CreateJavaInterface() is expected to be called just once for a given implementer + jassert (implementer->invocationHandler == nullptr); + + implementer->invocationHandler = GlobalRef (invocationHandler); + + return LocalRef (env->CallStaticObjectMethod (JavaProxy, JavaProxy.newProxyInstance, + classLoader.get(), classArray.get(), + invocationHandler.get())); +} + +LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, + const StringArray& interfaceNames) +{ + return CreateJavaInterface (implementer, interfaceNames, + LocalRef (getEnv()->NewObject (JavaObject, + JavaObject.constructor))); +} + +LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, + const String& interfaceName) +{ + return CreateJavaInterface (implementer, StringArray (interfaceName)); +} + +AndroidInterfaceImplementer::~AndroidInterfaceImplementer() +{ + clear(); +} + +void AndroidInterfaceImplementer::clear() +{ + if (invocationHandler != nullptr) + getEnv()->CallVoidMethod (invocationHandler, + JuceInvocationHandler.clear); +} + +jobject AndroidInterfaceImplementer::invoke (jobject /*proxy*/, jobject method, jobjectArray args) +{ + auto* env = getEnv(); + return env->CallObjectMethod (method, JavaMethod.invoke, javaSubClass.get(), args); +} + +jobject juce_invokeImplementer (JNIEnv*, jobject /*object*/, jlong host, jobject proxy, + jobject method, jobjectArray args) +{ + if (auto* myself = reinterpret_cast (host)) + return myself->invoke (proxy, method, args); + + return nullptr; +} + +void juce_dispatchDelete (JNIEnv*, jobject /*object*/, jlong host) +{ + if (auto* myself = reinterpret_cast (host)) + delete myself; +} + +//============================================================================== +jobject ActivityLifecycleCallbacks::invoke (jobject proxy, jobject method, jobjectArray args) +{ + auto* env = getEnv(); + + auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); + + auto activity = env->GetArrayLength (args) > 0 ? env->GetObjectArrayElement (args, 0) : (jobject) nullptr; + auto bundle = env->GetArrayLength (args) > 1 ? env->GetObjectArrayElement (args, 1) : (jobject) nullptr; + + if (methodName == "onActivityCreated") { onActivityCreated (activity, bundle); return nullptr; } + else if (methodName == "onActivityDestroyed") { onActivityDestroyed (activity); return nullptr; } + else if (methodName == "onActivityPaused") { onActivityPaused (activity); return nullptr; } + else if (methodName == "onActivityResumed") { onActivityResumed (activity); return nullptr; } + else if (methodName == "onActivitySaveInstanceState") { onActivitySaveInstanceState (activity, bundle); return nullptr; } + else if (methodName == "onActivityStarted") { onActivityStarted (activity); return nullptr; } + else if (methodName == "onActivityStopped") { onActivityStopped (activity); return nullptr; } + + return AndroidInterfaceImplementer::invoke (proxy, method, args); +} + +//============================================================================== +int getAndroidSDKVersion() +{ + // this is used so often that we need to cache this + static int sdkVersion = [] + { + // don't use any jni helpers as they might not have been initialised yet + // when this method is used + auto* env = getEnv(); + + auto buildVersion = env->FindClass ("android/os/Build$VERSION"); + jassert (buildVersion != 0); + + auto sdkVersionField = env->GetStaticFieldID (buildVersion, "SDK_INT", "I"); + jassert (sdkVersionField != 0); + + return env->GetStaticIntField (buildVersion, sdkVersionField); + }(); + + return sdkVersion; +} + +bool isPermissionDeclaredInManifest (const String& requestedPermission) +{ + auto* env = getEnv(); + + LocalRef pkgManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager)); + LocalRef pkgName (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)); + LocalRef pkgInfo (env->CallObjectMethod (pkgManager.get(), AndroidPackageManager.getPackageInfo, + pkgName.get(), 0x00001000 /* PERMISSIONS */)); + + LocalRef permissions ((jobjectArray) env->GetObjectField (pkgInfo.get(), AndroidPackageInfo.requestedPermissions)); + int n = env->GetArrayLength (permissions); + + for (int i = 0; i < n; ++i) + { + LocalRef jstr ((jstring) env->GetObjectArrayElement (permissions, i)); + String permissionId (juceString (jstr)); + + if (permissionId == requestedPermission) + return true; + } + + return false; +} + +//============================================================================== +// This byte-code is generated from native/java/com/roli/juce/FragmentOverlay.java with min sdk version 16 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const uint8 javaFragmentOverlay[] = +{31,139,8,8,197,105,229,91,0,3,70,114,97,103,109,101,110,116,79,118,101,114,108,97,121,46,100,101,120,0,133,149,93,136, +28,69,16,199,171,231,243,62,39,235,93,60,206,243,244,214,136,73,4,201,156,104,32,186,107,184,36,34,236,58,248,145,11,251, +112,241,101,216,29,215,137,123,51,155,153,217,35,1,65,61,2,201,131,8,10,126,97,2,17,84,16,204,91,30,228,240,73,130,95,40, +104,124,9,137,47,9,248,230,23,8,65,52,130,255,234,238,201,133,35,226,178,191,169,234,234,238,170,234,154,158,238,78,116, +100,100,254,129,157,116,170,242,197,47,171,219,126,253,250,218,79,107,167,30,234,189,251,229,123,127,239,158,219,187,86, +124,127,193,33,234,19,209,145,214,131,19,164,127,187,96,187,151,148,125,4,108,22,74,214,33,241,167,179,120,84,32,63,213, +237,186,65,244,130,69,244,12,228,121,147,232,34,248,29,252,1,174,130,191,192,63,224,118,140,217,9,154,224,121,240,34,88, +5,39,192,171,224,117,240,14,56,13,62,0,31,129,51,224,51,240,21,56,15,46,128,203,224,55,240,39,112,108,162,105,48,15,30,6, +77,240,44,56,1,94,3,167,193,25,176,6,62,7,223,2,164,73,72,135,176,76,114,193,16,24,214,107,29,5,147,188,102,0,247,114, +125,199,48,216,214,109,210,99,92,173,143,105,253,21,140,25,215,250,219,208,61,173,191,15,125,147,214,63,54,85,221,88,255, +4,250,45,90,63,7,125,66,235,223,200,88,130,166,136,243,52,100,12,3,217,221,169,219,91,116,30,51,196,227,84,63,203,91,181, +156,38,53,255,54,41,77,154,149,210,161,59,164,84,126,108,172,120,78,74,139,170,82,186,116,151,158,191,69,74,155,238,38, +181,102,65,164,163,40,157,127,67,142,146,38,44,108,251,193,86,53,236,87,120,44,103,190,84,229,10,148,253,151,116,127,217, +147,84,28,140,243,80,71,75,190,131,43,182,90,255,34,54,220,20,130,221,15,55,187,208,187,152,161,38,135,197,49,241,134, +251,225,138,51,12,95,30,241,76,94,255,207,152,195,107,74,171,130,14,192,163,11,235,24,205,136,41,74,170,38,170,60,74,75, +11,240,184,112,163,71,87,182,251,11,136,251,180,39,223,163,138,127,245,127,226,187,50,254,184,140,207,181,229,189,195,19, +249,253,165,21,206,231,166,113,230,55,145,37,60,93,55,71,239,57,210,82,233,195,178,46,66,83,238,55,238,45,117,30,97,72, +221,210,99,156,122,156,196,197,110,218,252,88,22,118,151,163,164,120,114,37,202,122,225,209,29,135,194,149,144,68,131,68, +147,140,102,64,34,160,217,32,76,58,89,26,119,252,176,223,247,31,141,195,94,218,45,103,213,104,250,122,111,59,77,10,152, +252,134,20,53,154,188,222,147,230,254,222,65,210,233,69,53,154,11,218,233,178,159,165,189,216,63,52,104,71,254,134,240, +53,154,8,56,3,191,23,38,93,127,177,200,226,164,91,35,209,34,171,213,104,4,252,12,2,50,90,77,178,91,77,54,176,128,197,108, +53,217,12,14,54,104,242,224,77,92,216,237,94,154,71,228,182,251,253,3,207,197,57,89,157,176,8,201,237,196,249,114,156, +231,52,214,141,138,61,89,119,192,169,228,228,162,21,164,73,23,230,44,76,138,253,81,62,232,193,92,73,147,61,237,34,94,137, +139,163,202,68,83,27,45,79,132,104,69,52,148,38,251,178,40,44,34,242,74,77,247,204,164,201,254,232,240,32,202,139,167, +162,140,67,199,105,146,107,111,213,255,238,211,179,221,52,89,44,194,172,160,113,173,104,251,104,127,125,2,141,102,202, +201,190,180,19,209,72,38,231,75,221,206,11,78,201,42,184,0,46,185,158,113,95,141,118,64,62,94,167,237,230,214,109,211, +174,119,252,77,26,19,219,93,175,126,238,248,18,85,205,173,247,204,194,246,22,190,57,215,123,4,22,18,54,62,111,235,229, +151,172,31,45,123,21,39,201,13,216,226,154,101,138,147,182,33,190,3,39,29,72,103,124,195,55,207,178,188,19,120,63,150, +247,130,73,235,119,67,185,103,249,126,224,179,163,188,35,28,90,191,39,68,85,181,249,174,16,21,117,46,240,249,106,84,149, +127,190,63,76,61,134,207,21,62,160,68,121,230,84,148,206,247,211,191,48,134,254,198,216,6,0,0}; + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (construct, "", "()V") \ + METHOD (close, "close", "()V") \ + CALLBACK (FragmentOverlay::onActivityResultNative, "onActivityResultNative", "(JIILandroid/content/Intent;)V") \ + CALLBACK (FragmentOverlay::onCreateNative, "onCreateNative", "(JLandroid/os/Bundle;)V") \ + CALLBACK (FragmentOverlay::onStartNative, "onStartNative", "(J)V") \ + CALLBACK (FragmentOverlay::onRequestPermissionsResultNative, "onRequestPermissionsResultNative", "(JI[Ljava/lang/String;[I)V") + + DECLARE_JNI_CLASS_WITH_BYTECODE (JuceFragmentOverlay, "com/roli/juce/FragmentOverlay", 16, javaFragmentOverlay, sizeof(javaFragmentOverlay)) +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (show, "show", "(Landroid/app/FragmentManager;Ljava/lang/String;)V") + + DECLARE_JNI_CLASS (AndroidDialogFragment, "android/app/DialogFragment") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +FragmentOverlay::FragmentOverlay() + : native (LocalRef (getEnv()->NewObject (JuceFragmentOverlay, JuceFragmentOverlay.construct))) +{} + +FragmentOverlay::~FragmentOverlay() +{ + auto* env = getEnv(); + + env->CallVoidMethod (native.get(), JuceFragmentOverlay.close); +} + +void FragmentOverlay::open() +{ + auto* env = getEnv(); + + LocalRef bundle (env->NewObject (AndroidBundle, AndroidBundle.constructor)); + env->CallVoidMethod (bundle.get(), AndroidBundle.putLong, javaString ("cppThis").get(), (jlong) this); + env->CallVoidMethod (native.get(), AndroidFragment.setArguments, bundle.get()); + + LocalRef fm (env->CallObjectMethod (getCurrentActivity().get(), AndroidActivity.getFragmentManager)); + env->CallVoidMethod (native.get(), AndroidDialogFragment.show, fm.get(), javaString ("FragmentOverlay").get()); +} + +void FragmentOverlay::onActivityResultNative (JNIEnv* env, jobject, jlong host, + jint requestCode, jint resultCode, jobject data) +{ + if (auto* myself = reinterpret_cast (host)) + myself->onActivityResult (requestCode, resultCode, LocalRef (env->NewLocalRef (data))); +} + +void FragmentOverlay::onCreateNative (JNIEnv* env, jobject, jlong host, jobject bundle) +{ + if (auto* myself = reinterpret_cast (host)) + myself->onCreated (LocalRef (env->NewLocalRef (bundle))); +} + +void FragmentOverlay::onStartNative (JNIEnv*, jobject, jlong host) +{ + if (auto* myself = reinterpret_cast (host)) + myself->onStart(); +} + +void FragmentOverlay::onRequestPermissionsResultNative (JNIEnv* env, jobject, jlong host, jint requestCode, + jobjectArray jPermissions, jintArray jGrantResults) +{ + if (auto* myself = reinterpret_cast (host)) + { + Array grantResults; + int n = (jGrantResults != nullptr ? env->GetArrayLength (jGrantResults) : 0); + + if (n > 0) + { + auto* data = env->GetIntArrayElements (jGrantResults, 0); + + for (int i = 0; i < n; ++i) + grantResults.add (data[i]); + + env->ReleaseIntArrayElements (jGrantResults, data, 0); + } + + myself->onRequestPermissionsResult (requestCode, + javaStringArrayToJuce (LocalRef (jPermissions)), + grantResults); + } +} + +jobject FragmentOverlay::getNativeHandle() +{ + return native.get(); +} + +//============================================================================== +class ActivityLauncher : public FragmentOverlay +{ +public: + ActivityLauncher (const LocalRef& intentToUse, + int requestCodeToUse, + std::function)> && callbackToUse) + : intent (intentToUse), requestCode (requestCodeToUse), callback (std::move (callbackToUse)) + {} + + void onStart() override + { + getEnv()->CallVoidMethod (getNativeHandle(), AndroidFragment.startActivityForResult, + intent.get(), requestCode); + } + + void onActivityResult (int activityRequestCode, int resultCode, LocalRef data) override + { + if (callback) + callback (activityRequestCode, resultCode, std::move (data)); + + getEnv()->CallVoidMethod (getNativeHandle(), JuceFragmentOverlay.close); + delete this; + } + +private: + GlobalRef intent; + int requestCode; + std::function)> callback; +}; + +void startAndroidActivityForResult (const LocalRef& intent, int requestCode, + std::function)> && callback) +{ + auto* activityLauncher = new ActivityLauncher (intent, requestCode, std::move (callback)); + activityLauncher->open(); +} + +//============================================================================== +bool androidHasSystemFeature (const String& property) +{ + LocalRef appContext (getAppContext()); + + if (appContext != nullptr) + { + auto* env = getEnv(); + + LocalRef packageManager (env->CallObjectMethod (appContext.get(), AndroidContext.getPackageManager)); + + if (packageManager != nullptr) + return env->CallBooleanMethod (packageManager.get(), + AndroidPackageManager.hasSystemFeature, + javaString (property).get()) != 0; + } + + // unable to get app's context + jassertfalse; + return false; +} + +String audioManagerGetProperty (const String& property) +{ + if (getAndroidSDKVersion() >= 17) + { + auto* env = getEnv(); + LocalRef audioManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, + javaString ("audio").get())); + + if (audioManager != nullptr) + { + LocalRef jProperty (javaString (property)); + + auto methodID = env->GetMethodID (AndroidAudioManager, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;"); + + if (methodID != nullptr) + return juceString (LocalRef ((jstring) env->CallObjectMethod (audioManager.get(), + methodID, + javaString (property).get()))); + } + } + + return {}; +} + +} diff --git a/modules/juce_core/native/juce_android_JNIHelpers.h b/modules/juce_core/native/juce_android_JNIHelpers.h index 5a6d83cd88..8b6a8d9dbc 100644 --- a/modules/juce_core/native/juce_android_JNIHelpers.h +++ b/modules/juce_core/native/juce_android_JNIHelpers.h @@ -23,42 +23,81 @@ namespace juce { -#if ! (defined (JUCE_ANDROID_ACTIVITY_CLASSNAME) && defined (JUCE_ANDROID_ACTIVITY_CLASSPATH)) - #error "The JUCE_ANDROID_ACTIVITY_CLASSNAME and JUCE_ANDROID_ACTIVITY_CLASSPATH macros must be set!" -#endif - //============================================================================== extern JNIEnv* getEnv() noexcept; -// You should rarely need to use this function. Only if you expect callbacks -// on a java thread which you did not create yourself. -extern void setEnv (JNIEnv* env) noexcept; +//============================================================================== +template +class LocalRef +{ +public: + explicit inline LocalRef() noexcept : obj (0) {} + explicit inline LocalRef (JavaType o) noexcept : obj (o) {} + inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} + inline LocalRef (LocalRef&& other) noexcept : obj (0) { std::swap (obj, other.obj); } + ~LocalRef() { clear(); } -/* @internal */ -extern JNIEnv* attachAndroidJNI() noexcept; + void clear() + { + if (obj != 0) + { + getEnv()->DeleteLocalRef (obj); + obj = 0; + } + } + + LocalRef& operator= (const LocalRef& other) + { + JavaType newObj = retain (other.obj); + clear(); + obj = newObj; + return *this; + } + + LocalRef& operator= (LocalRef&& other) + { + clear(); + std::swap (other.obj, obj); + return *this; + } + + inline operator JavaType() const noexcept { return obj; } + inline JavaType get() const noexcept { return obj; } + +private: + JavaType obj; + + static JavaType retain (JavaType obj) + { + return obj == 0 ? 0 : (JavaType) getEnv()->NewLocalRef (obj); + } +}; //============================================================================== class GlobalRef { public: - inline GlobalRef() noexcept : obj (0) {} - inline explicit GlobalRef (jobject o) : obj (retain (o)) {} - inline GlobalRef (const GlobalRef& other) : obj (retain (other.obj)) {} - inline GlobalRef (GlobalRef && other) noexcept : obj (0) { std::swap (other.obj, obj); } + inline GlobalRef() noexcept : obj (0) {} + inline explicit GlobalRef (const LocalRef& o) : obj (retain (o.get(), getEnv())) {} + inline explicit GlobalRef (const LocalRef& o, JNIEnv* env) : obj (retain (o.get(), env)) {} + inline GlobalRef (const GlobalRef& other) : obj (retain (other.obj, getEnv())) {} + inline GlobalRef (GlobalRef && other) noexcept : obj (0) { std::swap (other.obj, obj); } ~GlobalRef() { clear(); } - inline void clear() + + inline void clear() { if (obj != 0) clear (getEnv()); } + inline void clear (JNIEnv* env) { if (obj != 0) { - getEnv()->DeleteGlobalRef (obj); + env->DeleteGlobalRef (obj); obj = 0; } } inline GlobalRef& operator= (const GlobalRef& other) { - jobject newObj = retain (other.obj); + jobject newObj = retain (other.obj, getEnv()); clear(); obj = newObj; return *this; @@ -110,71 +149,33 @@ private: //============================================================================== jobject obj = 0; - static inline jobject retain (jobject obj) + static inline jobject retain (jobject obj, JNIEnv* env) { - return obj == 0 ? 0 : getEnv()->NewGlobalRef (obj); + return obj == 0 ? 0 : env->NewGlobalRef (obj); } }; -//============================================================================== -template -class LocalRef -{ -public: - explicit inline LocalRef() noexcept : obj (0) {} - explicit inline LocalRef (JavaType o) noexcept : obj (o) {} - inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} - inline LocalRef (LocalRef&& other) noexcept : obj (0) { std::swap (obj, other.obj); } - ~LocalRef() { clear(); } - - void clear() - { - if (obj != 0) - { - getEnv()->DeleteLocalRef (obj); - obj = 0; - } - } - - LocalRef& operator= (const LocalRef& other) - { - JavaType newObj = retain (other.obj); - clear(); - obj = newObj; - return *this; - } - - LocalRef& operator= (LocalRef&& other) - { - clear(); - std::swap (other.obj, obj); - return *this; - } - - inline operator JavaType() const noexcept { return obj; } - inline JavaType get() const noexcept { return obj; } - -private: - JavaType obj; - static JavaType retain (JavaType obj) - { - return obj == 0 ? 0 : (JavaType) getEnv()->NewLocalRef (obj); - } -}; +//============================================================================== +extern LocalRef getAppContext() noexcept; +extern LocalRef getCurrentActivity() noexcept; +extern LocalRef getMainActivity() noexcept; //============================================================================== +struct SystemJavaClassComparator; class JNIClassBase { public: - explicit JNIClassBase (const char* classPath); + explicit JNIClassBase (const char* classPath, int minSDK, const void* byteCode, size_t byteCodeSize); virtual ~JNIClassBase(); - inline operator jclass() const noexcept { return classRef; } + inline operator jclass() const noexcept { return classRef; } static void initialiseAllClasses (JNIEnv*); static void releaseAllClasses (JNIEnv*); + inline const char* getClassPath() const noexcept { return classPath; } + protected: virtual void initialiseFields (JNIEnv*) = 0; @@ -182,172 +183,146 @@ protected: jmethodID resolveStaticMethod (JNIEnv*, const char* methodName, const char* params); jfieldID resolveField (JNIEnv*, const char* fieldName, const char* signature); jfieldID resolveStaticField (JNIEnv*, const char* fieldName, const char* signature); + void resolveCallbacks (JNIEnv*, const Array&); private: + friend struct SystemJavaClassComparator; + const char* const classPath; - jclass classRef; + const void* byteCode; + size_t byteCodeSize; + + int minSDK; + jclass classRef = 0; static Array& getClasses(); void initialise (JNIEnv*); void release (JNIEnv*); + void tryLoadingClassWithClassLoader (JNIEnv* env, jobject classLoader); JUCE_DECLARE_NON_COPYABLE (JNIClassBase) }; //============================================================================== -#define CREATE_JNI_METHOD(methodID, stringName, params) methodID = resolveMethod (env, stringName, params); -#define CREATE_JNI_STATICMETHOD(methodID, stringName, params) methodID = resolveStaticMethod (env, stringName, params); -#define CREATE_JNI_FIELD(fieldID, stringName, signature) fieldID = resolveField (env, stringName, signature); -#define CREATE_JNI_STATICFIELD(fieldID, stringName, signature) fieldID = resolveStaticField (env, stringName, signature); -#define DECLARE_JNI_METHOD(methodID, stringName, params) jmethodID methodID; -#define DECLARE_JNI_FIELD(fieldID, stringName, signature) jfieldID fieldID; - -#define DECLARE_JNI_CLASS(CppClassName, javaPath) \ +#define CREATE_JNI_METHOD(methodID, stringName, params) methodID = resolveMethod (env, stringName, params); +#define CREATE_JNI_STATICMETHOD(methodID, stringName, params) methodID = resolveStaticMethod (env, stringName, params); +#define CREATE_JNI_FIELD(fieldID, stringName, signature) fieldID = resolveField (env, stringName, signature); +#define CREATE_JNI_STATICFIELD(fieldID, stringName, signature) fieldID = resolveStaticField (env, stringName, signature); +#define CREATE_JNI_CALLBACK(callbackName, stringName, signature) callbacks.add ({stringName, signature, (void*) callbackName}); +#define DECLARE_JNI_METHOD(methodID, stringName, params) jmethodID methodID; +#define DECLARE_JNI_FIELD(fieldID, stringName, signature) jfieldID fieldID; +#define DECLARE_JNI_CALLBACK(fieldID, stringName, signature) + +#define DECLARE_JNI_CLASS_WITH_BYTECODE(CppClassName, javaPath, minSDK, byteCodeData, byteCodeSize) \ class CppClassName ## _Class : public JNIClassBase \ { \ public: \ - CppClassName ## _Class() : JNIClassBase (javaPath) {} \ + CppClassName ## _Class() : JNIClassBase (javaPath, minSDK, byteCodeData, byteCodeSize) {} \ \ void initialiseFields (JNIEnv* env) \ { \ - ignoreUnused (env); \ - JNI_CLASS_MEMBERS (CREATE_JNI_METHOD, CREATE_JNI_STATICMETHOD, CREATE_JNI_FIELD, CREATE_JNI_STATICFIELD); \ + Array callbacks; \ + JNI_CLASS_MEMBERS (CREATE_JNI_METHOD, CREATE_JNI_STATICMETHOD, CREATE_JNI_FIELD, CREATE_JNI_STATICFIELD, CREATE_JNI_CALLBACK); \ + resolveCallbacks (env, callbacks); \ } \ \ - JNI_CLASS_MEMBERS (DECLARE_JNI_METHOD, DECLARE_JNI_METHOD, DECLARE_JNI_FIELD, DECLARE_JNI_FIELD) \ + JNI_CLASS_MEMBERS (DECLARE_JNI_METHOD, DECLARE_JNI_METHOD, DECLARE_JNI_FIELD, DECLARE_JNI_FIELD, DECLARE_JNI_CALLBACK) \ }; \ static CppClassName ## _Class CppClassName; - //============================================================================== -#if defined (__arm__) - #define JUCE_ARM_SOFT_FLOAT_ABI __attribute__ ((pcs("aapcs"))) -#else - #define JUCE_ARM_SOFT_FLOAT_ABI -#endif - -#define JUCE_JNI_CALLBACK(className, methodName, returnType, params) \ - extern "C" __attribute__ ((visibility("default"))) JUCE_ARM_SOFT_FLOAT_ABI returnType JUCE_JOIN_MACRO (JUCE_JOIN_MACRO (Java_, className), _ ## methodName) params - - +#define DECLARE_JNI_CLASS_WITH_MIN_SDK(CppClassName, javaPath, minSDK) \ + DECLARE_JNI_CLASS_WITH_BYTECODE (CppClassName, javaPath, minSDK, nullptr, 0) //============================================================================== -class AndroidSystem -{ -public: - AndroidSystem(); - - void initialise (JNIEnv*, jobject activity, jstring appFile, jstring appDataDir); - void shutdown (JNIEnv*); - - //============================================================================== - GlobalRef activity; - String appFile, appDataDir; - int screenWidth, screenHeight, dpi; -}; - -extern AndroidSystem android; +#define DECLARE_JNI_CLASS(CppClassName, javaPath) \ + DECLARE_JNI_CLASS_WITH_MIN_SDK (CppClassName, javaPath, 16) //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ - METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ - METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(JZ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ - METHOD (finish, "finish", "()V") \ - METHOD (getWindowManager, "getWindowManager", "()Landroid/view/WindowManager;") \ - METHOD (setRequestedOrientation, "setRequestedOrientation", "(I)V") \ - METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ - METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ - METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ - METHOD (renderGlyph, "renderGlyph", "(CCLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ - STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ - METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ - METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ - METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V") \ - METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ - STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ - STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \ - STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \ - STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \ - STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \ - STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \ - METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ - METHOD (getTypeFaceFromByteArray, "getTypeFaceFromByteArray", "([B)Landroid/graphics/Typeface;") \ - METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \ - METHOD (getScreenSaver, "getScreenSaver", "()Z") \ - METHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager;") \ - METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ - STATICMETHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ - METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ - METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ - METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ - METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ - METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ - METHOD (isPermissionDeclaredInManifestString, "isPermissionDeclaredInManifest", "(Ljava/lang/String;)Z") \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getAssets, "getAssets", "()Landroid/content/res/AssetManager;") \ METHOD (getSystemService, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;") \ METHOD (getPackageManager, "getPackageManager", "()Landroid/content/pm/PackageManager;") \ METHOD (getPackageName, "getPackageName", "()Ljava/lang/String;") \ METHOD (getResources, "getResources", "()Landroid/content/res/Resources;") \ - METHOD (createInvocationHandler, "createInvocationHandler", "(J)Ljava/lang/reflect/InvocationHandler;") \ - METHOD (invocationHandlerContextDeleted, "invocationHandlerContextDeleted", "(Ljava/lang/reflect/InvocationHandler;)V") \ METHOD (bindService, "bindService", "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z") \ METHOD (unbindService, "unbindService", "(Landroid/content/ServiceConnection;)V") \ + METHOD (startActivity, "startActivity", "(Landroid/content/Intent;)V") \ + METHOD (getContentResolver, "getContentResolver", "()Landroid/content/ContentResolver;") \ + METHOD (getApplicationContext, "getApplicationContext", "()Landroid/content/Context;") \ + METHOD (getApplicationInfo, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;") \ + METHOD (checkCallingOrSelfPermission, "checkCallingOrSelfPermission", "(Ljava/lang/String;)I") \ + METHOD (getCacheDir, "getCacheDir", "()Ljava/io/File;") + +DECLARE_JNI_CLASS (AndroidContext, "android/content/Context") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (finish, "finish", "()V") \ + METHOD (getWindowManager, "getWindowManager", "()Landroid/view/WindowManager;") \ + METHOD (setRequestedOrientation, "setRequestedOrientation", "(I)V") \ METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \ METHOD (moveTaskToBack, "moveTaskToBack", "(Z)Z") \ - METHOD (startActivity, "startActivity", "(Landroid/content/Intent;)V") \ METHOD (startActivityForResult, "startActivityForResult", "(Landroid/content/Intent;I)V") \ - METHOD (getContentResolver, "getContentResolver", "()Landroid/content/ContentResolver;") \ - METHOD (addAppPausedResumedListener, "addAppPausedResumedListener", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener;J)V") \ - METHOD (removeAppPausedResumedListener, "removeAppPausedResumedListener", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener;J)V") + METHOD (getFragmentManager, "getFragmentManager", "()Landroid/app/FragmentManager;") \ + METHOD (setContentView, "setContentView", "(Landroid/view/View;)V") \ + METHOD (getWindow, "getWindow", "()Landroid/view/Window;") + +DECLARE_JNI_CLASS (AndroidActivity, "android/app/Activity") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (startActivityForResult, "startActivityForResult", "(Landroid/content/Intent;I)V") \ + METHOD (setArguments, "setArguments", "(Landroid/os/Bundle;)V") -DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH) +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidFragment, "android/app/Fragment", 11) #undef JNI_CLASS_MEMBERS //============================================================================== -#if __ANDROID_API__ >= 21 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (build, "build", "()Landroid/media/AudioAttributes;") \ METHOD (constructor, "", "()V") \ METHOD (setContentType, "setContentType", "(I)Landroid/media/AudioAttributes$Builder;") \ METHOD (setUsage, "setUsage", "(I)Landroid/media/AudioAttributes$Builder;") -DECLARE_JNI_CLASS (AndroidAudioAttributesBuilder, "android/media/AudioAttributes$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidAudioAttributesBuilder, "android/media/AudioAttributes$Builder", 21) #undef JNI_CLASS_MEMBERS -#endif -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (abandonAudioFocus, "abandonAudioFocus", "(Landroid/media/AudioManager$OnAudioFocusChangeListener;)I") \ METHOD (requestAudioFocus, "requestAudioFocus", "(Landroid/media/AudioManager$OnAudioFocusChangeListener;II)I") DECLARE_JNI_CLASS (AndroidAudioManager, "android/media/AudioManager") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (createBitmap, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;") \ STATICMETHOD (createBitmapFrom, "createBitmap", "(Landroid/graphics/Bitmap;IIIILandroid/graphics/Matrix;Z)Landroid/graphics/Bitmap;") \ METHOD (compress, "compress", "(Landroid/graphics/Bitmap$CompressFormat;ILjava/io/OutputStream;)Z") \ METHOD (getHeight, "getHeight", "()I") \ METHOD (getWidth, "getWidth", "()I") \ METHOD (recycle, "recycle", "()V") \ - METHOD (setPixel, "setPixel", "(III)V") + METHOD (setPixel, "setPixel", "(III)V") \ + METHOD (getPixels, "getPixels", "([IIIIIII)V") DECLARE_JNI_CLASS (AndroidBitmap, "android/graphics/Bitmap") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (valueOf, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;") DECLARE_JNI_CLASS (AndroidBitmapConfig, "android/graphics/Bitmap$Config") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (decodeByteArray, "decodeByteArray", "([BII)Landroid/graphics/Bitmap;") DECLARE_JNI_CLASS (AndroidBitmapFactory, "android/graphics/BitmapFactory") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (containsKey, "containsKey", "(Ljava/lang/String;)Z") \ METHOD (get, "get", "(Ljava/lang/String;)Ljava/lang/Object;") \ @@ -373,7 +348,7 @@ DECLARE_JNI_CLASS (AndroidBitmapFactory, "android/graphics/BitmapFactory") DECLARE_JNI_CLASS (AndroidBundle, "android/os/Bundle") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (dumpReferenceTables, "dumpReferenceTables", "()V") DECLARE_JNI_CLASS (AndroidDebug, "android/os/Debug") @@ -381,13 +356,15 @@ DECLARE_JNI_CLASS (AndroidBundle, "android/os/Bundle") #define JUCE_LOG_JNI_REFERENCES_TABLE getEnv()->CallStaticVoidMethod (AndroidDebug, AndroidDebug.dumpReferenceTables); -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (getRotation, "getRotation", "()I") +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (getRotation, "getRotation", "()I") \ + METHOD (getMetrics, "getMetrics", "(Landroid/util/DisplayMetrics;)V" ) \ + METHOD (getSize, "getSize", "(Landroid/graphics/Point;)V" ) DECLARE_JNI_CLASS (AndroidDisplay, "android/view/Display") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (constructorWithLooper, "", "(Landroid/os/Looper;)V") \ METHOD (post, "post", "(Ljava/lang/Runnable;)Z") \ @@ -396,22 +373,22 @@ DECLARE_JNI_CLASS (AndroidDisplay, "android/view/Display") DECLARE_JNI_CLASS (AndroidHandler, "android/os/Handler") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;)V") \ METHOD (getLooper, "getLooper", "()Landroid/os/Looper;") \ METHOD (join, "join", "()V") \ - METHOD (quitSafely, "quitSafely", "()Z") \ METHOD (start, "start", "()V") DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (createChooser, "createChooser", "(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;") \ METHOD (addCategory, "addCategory", "(Ljava/lang/String;)Landroid/content/Intent;") \ METHOD (constructor, "", "()V") \ METHOD (constructorWithContextAndClass, "", "(Landroid/content/Context;Ljava/lang/Class;)V") \ METHOD (constructWithString, "", "(Ljava/lang/String;)V") \ + METHOD (constructWithUri, "", "(Ljava/lang/String;Landroid/net/Uri;)V") \ METHOD (getAction, "getAction", "()Ljava/lang/String;") \ METHOD (getCategories, "getCategories", "()Ljava/util/Set;") \ METHOD (getData, "getData", "()Landroid/net/Uri;") \ @@ -432,23 +409,41 @@ DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread") DECLARE_JNI_CLASS (AndroidIntent, "android/content/Intent") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (postRotate, "postRotate", "(FFF)Z") \ METHOD (postScale, "postScale", "(FFFF)Z") \ METHOD (postTranslate, "postTranslate", "(FF)Z") \ - METHOD (setValues, "setValues", "([F)V") + METHOD (setValues, "setValues", "([F)V") \ + METHOD (mapRect, "mapRect", "(Landroid/graphics/RectF;)Z") DECLARE_JNI_CLASS (AndroidMatrix, "android/graphics/Matrix") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (getPackageInfo, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;") +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (getPackageInfo, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;") \ + METHOD (resolveActivity, "resolveActivity", "(Landroid/content/Intent;I)Landroid/content/pm/ResolveInfo;") \ + METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z") DECLARE_JNI_CLASS (AndroidPackageManager, "android/content/pm/PackageManager") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + FIELD (requestedPermissions, "requestedPermissions", "[Ljava/lang/String;") \ + FIELD (activities, "activities", "[Landroid/content/pm/ActivityInfo;") \ + FIELD (providers, "providers", "[Landroid/content/pm/ProviderInfo;") + + DECLARE_JNI_CLASS (AndroidPackageInfo, "android/content/pm/PackageInfo") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + FIELD (name, "name", "Ljava/lang/String;") \ + FIELD (packageName, "packageName", "Ljava/lang/String;") + + DECLARE_JNI_CLASS (AndroidPackageItemInfo, "android/content/pm/PackageItemInfo") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(I)V") \ METHOD (setColor, "setColor", "(I)V") \ METHOD (setAlpha, "setAlpha", "(I)V") \ @@ -459,56 +454,76 @@ DECLARE_JNI_CLASS (AndroidPackageManager, "android/content/pm/PackageManager") METHOD (getTextWidths, "getTextWidths", "(Ljava/lang/String;[F)I") \ METHOD (setTextScaleX, "setTextScaleX", "(F)V") \ METHOD (getTextPath, "getTextPath", "(Ljava/lang/String;IIFFLandroid/graphics/Path;)V") \ + METHOD (getCharsPath, "getTextPath", "([CIIFFLandroid/graphics/Path;)V") \ METHOD (setShader, "setShader", "(Landroid/graphics/Shader;)Landroid/graphics/Shader;") \ DECLARE_JNI_CLASS (AndroidPaint, "android/graphics/Paint") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (create, "", "(Landroid/graphics/Bitmap;)V") \ + METHOD (setMatrix, "setMatrix", "(Landroid/graphics/Matrix;)V") \ + METHOD (drawPath, "drawPath", "(Landroid/graphics/Path;Landroid/graphics/Paint;)V") \ + METHOD (drawBitmap, "drawBitmap", "([IIIFFIIZLandroid/graphics/Paint;)V") \ + METHOD (getClipBounds, "getClipBounds", "()Landroid/graphics/Rect;") + + DECLARE_JNI_CLASS (AndroidCanvas, "android/graphics/Canvas") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getActivity, "getActivity", "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;") \ METHOD (getIntentSender, "getIntentSender", "()Landroid/content/IntentSender;") DECLARE_JNI_CLASS (AndroidPendingIntent, "android/app/PendingIntent") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (toString, "toString", "()Ljava/lang/String;") -DECLARE_JNI_CLASS (AndroidRange, "android/util/Range") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidRange, "android/util/Range", 21) +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (create, "", "(II)V") \ + FIELD (x, "x", "I") \ + FIELD (y, "y", "I") + +DECLARE_JNI_CLASS (AndroidPoint, "android/graphics/Point") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(IIII)V") \ FIELD (left, "left", "I") \ FIELD (right, "right", "I") \ FIELD (top, "top", "I") \ - FIELD (bottom, "bottom", "I") \ + FIELD (bottom, "bottom", "I") DECLARE_JNI_CLASS (AndroidRect, "android/graphics/Rect") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getIdentifier, "getIdentifier", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I") \ METHOD (openRawResourceFd, "openRawResourceFd", "(I)Landroid/content/res/AssetFileDescriptor;") DECLARE_JNI_CLASS (AndroidResources, "android/content/res/Resources") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getHeight, "getHeight", "()I") \ METHOD (getWidth, "getWidth", "()I") -DECLARE_JNI_CLASS (AndroidSize, "android/util/Size") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidSize, "android/util/Size", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (parse, "parse", "(Ljava/lang/String;)Landroid/net/Uri;") \ METHOD (toString, "toString", "()Ljava/lang/String;") DECLARE_JNI_CLASS (AndroidUri, "android/net/Uri") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (construct, "", "(Landroid/content/Context;)V") \ METHOD (layout, "layout", "(IIII)V") \ METHOD (getLeft, "getLeft", "()I") \ METHOD (getTop, "getTop", "()I") \ @@ -520,26 +535,30 @@ DECLARE_JNI_CLASS (AndroidUri, "android/net/Uri") METHOD (requestFocus, "requestFocus", "()Z") \ METHOD (hasFocus, "hasFocus", "()Z") \ METHOD (invalidate, "invalidate", "(IIII)V") \ - METHOD (setVisibility, "setVisibility", "(I)V") + METHOD (setVisibility, "setVisibility", "(I)V") \ + METHOD (setLayoutParams, "setLayoutParams", "(Landroid/view/ViewGroup$LayoutParams;)V") \ + METHOD (findViewById, "findViewById", "(I)Landroid/view/View;") \ + METHOD (getRootView, "getRootView", "()Landroid/view/View;") \ + METHOD (addOnLayoutChangeListener, "addOnLayoutChangeListener", "(Landroid/view/View$OnLayoutChangeListener;)V") DECLARE_JNI_CLASS (AndroidView, "android/view/View") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addView, "addView", "(Landroid/view/View;)V") \ METHOD (removeView, "removeView", "(Landroid/view/View;)V") DECLARE_JNI_CLASS (AndroidViewGroup, "android/view/ViewGroup") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getDefaultDisplay, "getDefaultDisplay", "()Landroid/view/Display;") DECLARE_JNI_CLASS (AndroidWindowManager, "android/view/WindowManager") #undef JNI_CLASS_MEMBERS //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(I)V") \ METHOD (add, "add", "(Ljava/lang/Object;)Z") \ METHOD (iterator, "iterator", "()Ljava/util/Iterator;") \ @@ -549,27 +568,28 @@ DECLARE_JNI_CLASS (AndroidWindowManager, "android/view/WindowManager") DECLARE_JNI_CLASS (JavaArrayList, "java/util/ArrayList") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (valueOf, "valueOf", "(Z)Ljava/lang/Boolean;") \ METHOD (booleanValue, "booleanValue", "()Z") DECLARE_JNI_CLASS (JavaBoolean, "java/lang/Boolean") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (get, "get", "([B)Ljava/nio/ByteBuffer;") \ - METHOD (remaining, "remaining", "()I") +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (get, "get", "([B)Ljava/nio/ByteBuffer;") \ + METHOD (remaining, "remaining", "()I") \ + STATICMETHOD (wrap, "wrap", "([B)Ljava/nio/ByteBuffer;") DECLARE_JNI_CLASS (JavaByteBuffer, "java/nio/ByteBuffer") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (toString, "toString", "()Ljava/lang/String;") DECLARE_JNI_CLASS (JavaCharSequence, "java/lang/CharSequence") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (forName, "forName", "(Ljava/lang/String;)Ljava/lang/Class;") \ METHOD (getName, "getName", "()Ljava/lang/String;") \ METHOD (getModifiers, "getModifiers", "()I") \ @@ -589,13 +609,13 @@ DECLARE_JNI_CLASS (JavaCharSequence, "java/lang/CharSequence") DECLARE_JNI_CLASS (JavaClass, "java/lang/Class") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (toString, "toString", "()Ljava/lang/String;") DECLARE_JNI_CLASS (JavaEnum, "java/lang/Enum") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;)V") \ METHOD (getAbsolutePath, "getAbsolutePath", "()Ljava/lang/String;") \ METHOD (length, "length", "()J") @@ -603,7 +623,7 @@ DECLARE_JNI_CLASS (JavaEnum, "java/lang/Enum") DECLARE_JNI_CLASS (JavaFile, "java/io/File") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;)V") \ METHOD (close, "close", "()V") \ METHOD (read, "read", "([B)I") @@ -611,7 +631,7 @@ DECLARE_JNI_CLASS (JavaFile, "java/io/File") DECLARE_JNI_CLASS (JavaFileInputStream, "java/io/FileInputStream") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;)V") \ METHOD (close, "close", "()V") \ METHOD (write, "write", "([BII)V") @@ -619,14 +639,14 @@ DECLARE_JNI_CLASS (JavaFileInputStream, "java/io/FileInputStream") DECLARE_JNI_CLASS (JavaFileOutputStream, "java/io/FileOutputStream") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (constructorWithCapacity, "", "(I)V") DECLARE_JNI_CLASS (JavaHashMap, "java/util/HashMap") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (parseInt, "parseInt", "(Ljava/lang/String;I)I") \ STATICMETHOD (valueOf, "valueOf", "(I)Ljava/lang/Integer;") \ METHOD (intValue, "intValue", "()I") @@ -634,27 +654,27 @@ DECLARE_JNI_CLASS (JavaHashMap, "java/util/HashMap") DECLARE_JNI_CLASS (JavaInteger, "java/lang/Integer") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (hasNext, "hasNext", "()Z") \ METHOD (next, "next", "()Ljava/lang/Object;") DECLARE_JNI_CLASS (JavaIterator, "java/util/Iterator") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (get, "get", "(I)Ljava/lang/Object;") \ METHOD (size, "size", "()I") DECLARE_JNI_CLASS (JavaList, "java/util/List") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(J)V") DECLARE_JNI_CLASS (JavaLong, "java/lang/Long") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (get, "get", "(Ljava/lang/Object;)Ljava/lang/Object;") \ METHOD (keySet, "keySet", "()Ljava/util/Set;") \ METHOD (put, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") @@ -662,7 +682,7 @@ DECLARE_JNI_CLASS (JavaLong, "java/lang/Long") DECLARE_JNI_CLASS (JavaMap, "java/util/Map") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getName, "getName", "()Ljava/lang/String;") \ METHOD (getModifiers, "getModifiers", "()I") \ METHOD (getParameterTypes, "getParameterTypes", "()[Ljava/lang/Class;") \ @@ -675,7 +695,7 @@ DECLARE_JNI_CLASS (JavaMethod, "java/lang/reflect/Method") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (getClass, "getClass", "()Ljava/lang/Class;") \ METHOD (toString, "toString", "()Ljava/lang/String;") @@ -683,7 +703,7 @@ DECLARE_JNI_CLASS (JavaMethod, "java/lang/reflect/Method") DECLARE_JNI_CLASS (JavaObject, "java/lang/Object") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (contains, "contains", "(Ljava/lang/Object;)Z") \ METHOD (iterator, "iterator", "()Ljava/util/Iterator;") \ METHOD (size, "size", "()I") @@ -691,13 +711,45 @@ DECLARE_JNI_CLASS (JavaObject, "java/lang/Object") DECLARE_JNI_CLASS (JavaSet, "java/util/Set") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (concat, "concat", "(Ljava/lang/String;)Ljava/lang/String;") \ METHOD (getBytes, "getBytes", "()[B") DECLARE_JNI_CLASS (JavaString, "java/lang/String") #undef JNI_CLASS_MEMBERS +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) +DECLARE_JNI_CLASS (AndroidBuild, "android/os/Build") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) +DECLARE_JNI_CLASS (AndroidBuildVersion, "android/os/Build$VERSION") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (registerActivityLifecycleCallbacks, "registerActivityLifecycleCallbacks", "(Landroid/app/Application$ActivityLifecycleCallbacks;)V") \ + METHOD (unregisterActivityLifecycleCallbacks, "unregisterActivityLifecycleCallbacks", "(Landroid/app/Application$ActivityLifecycleCallbacks;)V") + + DECLARE_JNI_CLASS (AndroidApplication, "android/app/Application") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(Landroid/content/Context;)V") \ + METHOD (getHolder, "getHolder", "()Landroid/view/SurfaceHolder;") \ + METHOD (getParent, "getParent", "()Landroid/view/ViewParent;") + + DECLARE_JNI_CLASS (AndroidSurfaceView, "android/view/SurfaceView") +#undef JNI_CLASS_MEMBERS + + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (getSurface, "getSurface", "()Landroid/view/Surface;") \ + METHOD (addCallback, "addCallback", "(Landroid/view/SurfaceHolder$Callback;)V") \ + METHOD (removeCallback, "removeCallback", "(Landroid/view/SurfaceHolder$Callback;)V") + + DECLARE_JNI_CLASS (AndroidSurfaceHolder, "android/view/SurfaceHolder") +#undef JNI_CLASS_MEMBERS + //============================================================================== namespace { @@ -778,6 +830,10 @@ namespace } } +//============================================================================== +int getAndroidSDKVersion(); +bool isPermissionDeclaredInManifest (const String& requestedPermission); + //============================================================================== class AndroidInterfaceImplementer; @@ -789,8 +845,8 @@ LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, LocalRef subclass); //============================================================================== -jobject juce_invokeImplementer (JNIEnv*, jlong, jobject, jobject, jobjectArray); -void juce_dispatchDelete (JNIEnv*, jlong); +jobject juce_invokeImplementer (JNIEnv*, jobject, jlong, jobject, jobject, jobjectArray); +void juce_dispatchDelete (JNIEnv*, jobject, jlong); //============================================================================== class AndroidInterfaceImplementer @@ -798,11 +854,12 @@ class AndroidInterfaceImplementer protected: virtual ~AndroidInterfaceImplementer(); virtual jobject invoke (jobject proxy, jobject method, jobjectArray args); + void clear(); //============================================================================== friend LocalRef CreateJavaInterface (AndroidInterfaceImplementer*, const StringArray&, LocalRef); - friend jobject juce_invokeImplementer (JNIEnv*, jlong, jobject, jobject, jobjectArray); - friend void juce_dispatchDelete (JNIEnv*, jlong); + friend jobject juce_invokeImplementer (JNIEnv*, jobject, jlong, jobject, jobject, jobjectArray); + friend void juce_dispatchDelete (JNIEnv*, jobject, jlong); private: GlobalRef javaSubClass; GlobalRef invocationHandler; @@ -814,23 +871,105 @@ LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, const String& interfaceName); //============================================================================== -class AppPausedResumedListener : public AndroidInterfaceImplementer +class ActivityLifecycleCallbacks : public AndroidInterfaceImplementer { public: - struct Owner + virtual void onActivityCreated (jobject /*activity*/, jobject /*bundle*/) {} + virtual void onActivityDestroyed (jobject /*activity*/) {} + virtual void onActivityPaused (jobject /*activity*/) {} + virtual void onActivityResumed (jobject /*activity*/) {} + virtual void onActivitySaveInstanceState (jobject /*activity*/, jobject /*bundle*/) {} + virtual void onActivityStarted (jobject /*activity*/) {} + virtual void onActivityStopped (jobject /*activity*/) {} + +private: + jobject invoke (jobject, jobject, jobjectArray) override; +}; + +//============================================================================== +struct SurfaceHolderCallback : AndroidInterfaceImplementer +{ + virtual ~SurfaceHolderCallback() {} + + virtual void surfaceChanged (LocalRef holder, int format, int width, int height) = 0; + virtual void surfaceCreated (LocalRef holder) = 0; + virtual void surfaceDestroyed (LocalRef holder) = 0; + +private: + jobject invoke (jobject proxy, jobject method, jobjectArray args) override { - virtual ~Owner() {} + auto* env = getEnv(); + auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); + LocalRef holder (env->GetArrayLength (args) > 0 ? env->GetObjectArrayElement (args, 0) : (jobject) nullptr); + + if (methodName == "surfaceChanged") + { + int intArgs[3]; - virtual void appPaused() = 0; - virtual void appResumed() = 0; - }; + for (int i = 0; i < 3; ++i) + { + LocalRef boxedType (env->GetObjectArrayElement (args, 1 + i)); + intArgs[i] = env->CallIntMethod (boxedType.get(), JavaInteger.intValue); + } - AppPausedResumedListener (Owner&); + surfaceChanged (std::move (holder), intArgs[0], intArgs[1], intArgs[2]); + } + else if (methodName == "surfaceCreated") + { + surfaceCreated (std::move (holder)); + } + else if (methodName == "surfaceDestroyed") + { + surfaceDestroyed (std::move (holder)); + } + else + { + return AndroidInterfaceImplementer::invoke (proxy, method, args); + } + + return nullptr; + } +}; + +//============================================================================== +class FragmentOverlay +{ +public: + FragmentOverlay(); + virtual ~FragmentOverlay(); + + void open(); - jobject invoke (jobject proxy, jobject method, jobjectArray args) override; + virtual void onCreated (LocalRef /*bundle*/) {} + virtual void onStart() {} + virtual void onRequestPermissionsResult (int /*requestCode*/, + const StringArray& /*permissions*/, + const Array& /*grantResults*/) {} + virtual void onActivityResult (int /*requestCode*/, int /*resultCode*/, LocalRef /*data*/) {} + +protected: + jobject getNativeHandle(); private: - Owner& owner; + + GlobalRef native; + +public: + /* internal: do not use */ + static void onActivityResultNative (JNIEnv*, jobject, jlong, jint, jint, jobject); + static void onCreateNative (JNIEnv*, jobject, jlong, jobject); + static void onStartNative (JNIEnv*, jobject, jlong); + static void onRequestPermissionsResultNative (JNIEnv*, jobject, jlong, jint, + jobjectArray, jintArray); }; +//============================================================================== +// Allows you to start an activity without requiring to have an activity +void startAndroidActivityForResult (const LocalRef& intent, int requestCode, + std::function)> && callback); + +//============================================================================== +bool androidHasSystemFeature (const String& property); +String audioManagerGetProperty (const String& property); + } // namespace juce diff --git a/modules/juce_core/native/juce_android_Network.cpp b/modules/juce_core/native/juce_android_Network.cpp index 13f97fecde..375b5fbc49 100644 --- a/modules/juce_core/native/juce_android_Network.cpp +++ b/modules/juce_core/native/juce_android_Network.cpp @@ -23,7 +23,168 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +//============================================================================== +// This byte-code is generated from native/java/com/roli/juce/JuceHTTPStream.java with min sdk version 16 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const uint8 javaJuceHttpStream[] = +{31,139,8,8,96,105,229,91,0,3,74,117,99,101,72,84,84,80,83,116,114,101,97,109,46,100,101,120,0,125,154,11,124,84,213,157, +199,255,231,222,185,119,30,153,153,220,76,30,147,132,60,38,33,132,4,18,38,32,40,146,128,200,75,32,65,17,6,20,146,181,29, +50,23,50,48,220,9,51,119,32,88,84,64,171,212,218,74,173,86,250,82,139,88,105,87,91,171,237,214,86,187,109,197,118,219, +218,90,109,245,179,174,187,125,91,119,237,167,187,186,218,118,221,246,227,102,127,231,49,153,137,77,33,124,239,255,127, +254,231,127,206,61,143,255,121,204,36,41,123,34,208,119,193,18,58,123,73,224,169,145,163,171,22,29,152,124,110,71,248, +214,145,193,207,157,62,245,139,197,215,175,242,156,233,36,26,39,162,137,237,139,35,164,254,253,114,14,209,11,36,237,139, +192,183,116,162,56,228,9,15,81,61,228,147,38,209,149,144,71,188,68,200,34,79,128,104,101,19,81,10,242,205,90,162,63,130, +183,193,95,192,255,1,173,142,200,0,33,80,5,162,160,27,92,12,54,131,173,224,42,48,2,118,1,27,92,11,174,3,199,192,205,224, +86,112,59,56,5,206,128,179,224,41,240,10,104,140,18,45,3,215,128,27,193,253,224,187,224,183,128,161,193,49,112,17,184,28, +236,6,71,193,221,224,33,240,44,120,5,188,13,194,13,68,237,96,0,92,5,246,129,227,224,62,240,8,248,1,248,37,120,19,248,27, +137,174,6,105,112,45,248,4,120,2,188,6,106,102,161,14,112,13,56,2,62,9,254,5,76,130,30,140,211,213,96,20,236,6,123,129,3, +242,224,125,224,40,184,13,156,4,31,5,119,131,79,130,123,193,253,224,113,240,36,248,30,120,17,252,28,188,10,94,7,255,3, +222,1,70,51,81,16,68,65,12,244,128,53,96,59,24,3,215,130,219,192,167,193,3,224,9,112,14,252,24,188,10,254,2,204,22,244, +17,88,160,17,116,128,249,96,17,184,8,108,2,127,7,28,112,61,184,25,220,13,206,128,175,128,239,128,31,129,127,6,175,129,55, +193,36,240,181,18,213,129,78,208,15,46,3,27,193,86,240,94,176,23,28,2,71,193,9,112,23,184,23,60,0,30,6,143,129,175,129, +103,193,207,192,239,192,155,224,207,128,98,232,59,168,4,115,64,63,216,14,118,131,125,96,28,28,4,71,192,205,224,36,184,23, +60,0,190,0,190,9,94,4,63,3,191,6,255,14,254,12,188,109,68,179,65,31,88,9,182,128,221,96,63,112,193,17,112,2,124,28,60,8, +190,8,190,14,190,15,126,14,222,2,122,59,226,2,52,130,118,208,11,150,131,203,64,2,100,64,1,92,7,110,5,31,1,167,64,16,221, +178,0,194,138,16,62,132,233,37,76,15,97,40,73,117,153,80,61,193,149,102,131,14,128,229,75,88,214,52,23,116,129,110,48,15, +204,7,61,160,23,44,32,185,166,251,192,66,181,206,47,0,139,193,18,112,33,184,8,44,5,23,131,126,48,0,86,128,75,192,74,112, +41,88,5,214,128,117,224,50,176,30,108,4,91,192,54,176,29,92,69,178,31,197,127,33,37,151,98,111,8,43,125,101,153,190,30, +122,165,210,55,215,202,254,51,149,230,251,143,1,70,96,143,148,213,203,245,98,249,6,165,187,202,167,88,87,173,242,91,170, +236,181,202,94,173,244,35,202,30,45,179,71,149,189,70,233,55,65,175,83,250,109,202,94,175,236,181,74,95,170,244,134,50, +157,207,219,157,181,178,28,215,63,165,222,213,92,214,254,150,50,189,181,76,111,47,211,103,151,245,133,207,239,67,170,126, +62,199,93,170,206,121,202,135,207,67,175,210,7,149,206,251,114,133,210,191,12,125,72,233,79,150,233,125,101,58,111,255, +38,165,63,13,125,179,210,249,248,95,174,244,31,150,249,252,91,173,60,27,122,213,248,23,235,121,165,86,198,196,2,213,158, +173,74,255,61,236,9,165,187,170,47,11,213,123,117,204,244,247,136,203,5,244,28,164,7,35,103,11,89,75,123,132,236,161,180, +144,97,250,176,144,221,244,77,226,241,209,68,41,33,165,159,161,252,12,140,88,65,200,249,116,147,144,81,186,69,201,15,8, +41,235,49,240,190,59,72,198,217,105,33,25,125,78,200,56,125,94,200,122,250,130,144,125,244,13,33,139,239,197,154,87,242, +135,64,163,42,186,75,180,191,141,76,33,189,244,30,33,131,66,122,208,30,83,200,86,250,146,40,215,41,210,188,221,59,84,59, +71,133,172,163,221,66,154,52,166,236,7,132,244,211,97,33,13,186,65,201,99,42,255,164,144,58,61,36,100,43,61,172,198,225, +31,136,175,153,118,241,158,16,118,11,46,195,200,79,10,233,17,254,149,72,31,18,114,30,253,68,196,93,5,237,83,241,119,187, +136,189,86,81,46,138,113,217,165,228,135,72,198,246,199,132,92,64,95,23,178,146,190,45,164,37,100,61,86,212,136,144,17, +218,47,164,44,87,143,145,146,82,150,175,87,254,13,234,61,13,88,101,35,66,134,233,25,226,123,225,108,97,111,68,254,19,196, +215,83,43,57,66,250,232,90,33,27,233,125,42,125,68,200,0,93,79,114,221,29,21,50,74,199,133,140,209,99,66,118,211,151,149, +252,138,178,127,85,200,14,122,92,200,57,244,53,226,107,85,142,87,51,118,85,41,235,233,140,144,178,93,173,24,247,247,11, +25,164,123,136,239,205,33,154,32,190,63,7,233,58,37,111,36,190,158,103,145,75,124,45,55,208,157,196,215,113,51,125,139, +248,94,221,66,25,33,155,232,227,196,215,116,47,189,87,200,160,168,103,190,26,143,249,248,145,233,110,122,148,248,158,46, +237,92,222,45,228,124,122,86,165,127,76,242,142,70,36,215,22,223,35,170,32,31,199,198,117,229,28,105,247,148,229,247,169, +252,151,144,191,87,229,243,120,102,84,218,47,121,254,219,200,63,174,242,121,253,63,197,193,243,50,248,77,135,244,253,131, +146,239,116,240,117,128,251,4,124,67,115,164,45,170,100,167,146,151,40,185,126,14,175,75,23,250,167,176,233,249,32,135, +145,24,209,24,141,91,179,68,132,242,92,94,223,153,118,185,71,182,179,32,109,139,49,156,83,22,37,98,68,7,44,175,136,112, +199,234,19,114,60,86,141,18,85,108,122,222,2,33,187,222,228,109,99,226,125,15,183,203,126,58,22,183,4,69,31,77,252,240, +188,71,219,229,121,34,219,48,204,52,26,214,12,26,214,61,52,236,209,105,216,48,197,202,225,237,50,232,71,237,242,60,76, +244,121,80,23,223,105,3,56,63,107,41,177,80,167,102,150,232,51,209,82,31,113,25,137,228,98,235,17,35,45,76,250,94,46,74, +105,200,247,10,233,88,189,194,98,40,139,1,75,237,84,205,155,197,104,132,80,122,46,158,37,91,215,159,144,90,40,83,76,156, +16,132,213,171,3,46,153,58,187,116,113,206,243,158,47,17,82,71,219,47,184,33,116,225,114,172,204,0,241,81,122,165,93,238, +221,188,207,62,140,114,15,180,77,240,28,62,230,71,170,86,216,121,138,231,205,71,106,163,72,85,96,95,226,55,129,32,59,96, +93,198,199,145,13,31,11,208,200,173,149,52,252,193,8,13,223,22,164,29,31,170,163,225,15,215,208,240,237,213,20,249,239, +29,199,162,120,179,69,59,142,90,120,75,21,13,31,13,209,182,27,43,41,113,83,132,18,239,15,210,22,92,253,19,183,132,200, +123,204,123,199,65,175,95,213,232,69,207,53,117,35,168,154,45,227,47,129,185,173,18,177,233,21,49,82,15,123,158,199,135, +175,25,49,176,12,235,216,177,6,240,142,160,175,213,215,68,222,155,90,61,77,20,241,57,177,11,113,18,56,177,197,244,7,60, +47,194,179,209,119,53,180,86,156,3,65,45,162,183,117,45,185,49,78,235,124,154,222,232,215,97,111,166,83,20,48,151,155, +237,194,230,224,114,236,163,128,111,201,205,141,34,29,241,59,184,34,158,50,131,140,167,94,242,155,204,137,197,80,34,232, +229,158,94,120,46,247,97,22,251,46,166,26,239,75,186,206,186,158,79,156,13,161,214,165,104,195,210,96,132,34,245,78,108, +9,215,67,60,70,23,34,166,130,134,131,15,47,47,34,21,69,108,85,121,66,34,42,60,232,85,136,26,253,126,244,172,6,245,207, +245,201,59,206,85,124,70,217,172,61,213,56,23,2,208,15,205,150,247,139,254,112,45,69,194,237,168,193,251,25,246,168,247, +156,247,5,246,91,239,159,124,94,139,52,95,21,249,252,17,58,24,48,197,216,93,28,254,175,201,234,112,95,161,235,119,33,22, +161,174,183,229,216,86,201,122,27,43,68,140,135,233,19,179,249,12,227,126,228,59,201,34,53,17,61,241,128,159,154,141,196, +233,10,180,110,16,30,1,109,41,214,71,139,193,22,85,106,145,200,120,44,136,115,36,168,13,159,14,83,226,116,13,69,204,196, +131,94,120,46,226,35,226,29,244,106,166,180,174,209,52,115,241,241,159,82,68,155,94,130,251,198,81,107,80,203,89,171,149, +92,195,165,153,179,86,96,133,242,183,61,77,57,235,18,232,65,214,202,218,160,95,202,35,5,246,55,38,115,214,42,165,255,110, +50,241,96,53,70,174,1,209,218,133,121,43,122,181,106,81,204,65,55,118,221,160,214,232,243,8,253,56,226,164,209,175,81, +177,116,171,86,67,7,98,98,39,214,74,190,84,244,213,28,107,158,104,153,19,107,199,154,43,175,185,209,27,128,87,19,229,208, +206,139,181,179,147,165,26,99,20,241,230,98,43,145,35,61,15,88,13,178,14,171,81,140,225,142,211,81,234,186,61,36,198,177, +155,70,38,67,106,44,22,137,220,37,199,219,228,104,122,18,159,149,86,140,132,57,104,106,30,233,213,11,175,153,243,101,125, +139,39,103,158,139,110,90,54,25,210,90,140,185,90,72,75,156,249,107,15,143,57,247,93,115,218,77,113,148,232,166,158,73, +212,188,170,155,188,147,124,255,240,99,87,170,22,123,168,87,220,131,121,236,116,136,61,171,90,124,214,184,1,233,199,196, +222,19,18,103,44,63,243,159,83,126,255,170,236,191,80,233,255,84,233,55,196,174,205,232,29,81,111,53,249,152,180,27,55, +132,30,101,191,103,55,116,126,149,189,206,232,247,140,94,103,215,119,190,95,19,70,47,198,129,159,41,175,206,150,159,119, +34,90,98,171,143,154,245,196,149,50,90,25,5,216,82,134,104,213,189,109,56,37,174,12,80,59,246,244,241,62,70,9,51,23,91, +139,207,56,216,113,174,172,69,126,2,207,156,181,65,236,65,173,44,76,93,175,133,88,139,62,151,97,119,106,235,250,141,120, +254,82,158,165,13,240,225,235,206,135,247,206,85,251,172,70,157,90,117,247,156,158,26,232,94,62,23,24,12,126,103,168,198, +135,155,72,232,32,171,64,127,2,214,197,150,33,44,149,214,75,161,16,243,193,182,133,89,116,97,243,255,78,114,61,193,48,31, +250,154,42,166,87,179,94,198,111,151,139,172,26,170,102,11,148,142,177,101,113,165,195,159,93,32,244,11,172,151,85,233, +106,106,212,55,32,26,59,112,195,14,176,151,194,44,80,178,35,150,251,230,208,194,112,160,242,162,215,159,82,254,124,222, +47,16,253,117,172,229,92,250,139,254,78,223,92,250,85,101,176,162,184,187,158,162,246,32,180,190,126,186,173,130,251,6, +17,27,139,238,184,103,234,189,17,93,104,30,148,180,58,49,70,1,143,211,55,155,22,122,132,21,177,226,195,213,178,57,34,189, +203,231,165,82,140,70,75,164,155,38,38,49,218,17,140,118,128,91,186,105,188,172,141,189,240,230,169,109,122,37,201,247, +68,164,52,130,66,110,49,235,100,218,27,66,204,84,83,241,36,225,182,97,212,192,235,95,57,41,107,230,250,170,73,18,247,243, +185,104,233,205,106,255,187,91,204,163,135,62,13,233,133,198,239,125,124,247,127,74,165,117,186,11,91,228,41,118,195,172, +239,176,187,24,169,91,12,209,137,14,185,135,110,141,85,137,207,177,69,251,135,59,100,172,108,137,69,113,103,183,166,206, +179,59,213,125,41,130,29,221,15,111,15,126,62,209,33,63,27,226,76,220,133,157,86,75,36,195,180,148,249,137,203,136,238, +172,172,163,3,55,6,168,69,91,142,246,108,221,85,69,199,60,79,172,218,9,89,73,24,51,13,99,198,186,222,98,36,239,86,30,181, +134,26,85,92,206,154,58,255,27,88,168,137,199,44,67,116,106,244,88,135,252,238,160,29,37,18,7,112,255,200,97,47,206,227, +157,46,110,47,133,90,58,104,242,154,18,57,209,158,3,21,20,169,118,98,67,136,171,8,27,134,119,139,214,110,54,34,50,230, +163,46,199,234,33,126,67,146,109,137,136,27,17,19,223,79,48,241,51,7,111,144,55,212,103,202,250,206,191,75,228,255,52,42, +73,55,32,199,242,8,228,77,1,121,103,44,126,47,192,207,175,147,1,57,166,167,32,239,83,249,229,119,95,158,111,170,122,252, +74,242,239,15,206,42,223,14,85,95,157,146,81,245,222,162,140,171,250,226,170,78,63,201,207,58,113,225,209,39,62,191,68, +85,217,5,101,109,43,239,131,69,53,194,110,168,54,151,202,71,133,125,158,242,227,159,65,25,201,123,167,108,67,84,148,105, +130,165,7,189,233,86,246,102,85,46,78,197,241,96,24,222,46,194,133,141,45,35,109,89,140,216,0,153,3,105,39,237,174,32, +109,69,63,121,86,244,119,111,39,107,117,214,113,236,81,55,157,117,98,118,46,151,205,81,24,22,215,118,220,222,33,219,217, +227,142,81,237,154,116,126,116,202,105,75,193,113,146,187,50,54,177,13,164,109,24,34,125,195,208,6,242,224,129,253,112, +35,85,111,44,140,218,235,19,137,205,91,221,156,157,220,191,96,111,242,96,146,216,16,105,112,210,185,143,54,132,34,67,120, +120,135,134,118,14,13,161,130,128,82,184,174,13,237,164,250,161,164,147,202,101,211,169,184,107,79,184,241,4,30,219,220, +116,38,223,79,177,161,209,236,254,120,46,155,73,199,247,226,53,241,233,239,234,88,216,79,139,207,239,49,99,71,250,169, +229,188,165,250,169,125,40,149,204,28,76,239,139,39,29,39,235,38,121,225,248,90,103,52,147,205,167,157,61,171,51,201,60, +218,54,251,124,62,155,108,119,44,155,226,47,250,107,167,13,104,79,78,85,210,54,67,254,38,123,255,46,229,96,195,165,121,6, +151,173,233,61,78,210,45,228,208,149,198,25,178,19,99,185,236,33,81,148,207,70,60,157,141,175,42,236,222,109,231,236,212, +6,103,188,224,22,123,89,59,149,189,225,138,181,19,163,246,56,47,60,205,92,238,93,55,101,190,162,224,150,217,235,165,61, +147,116,246,196,87,143,37,115,91,237,3,5,219,25,181,167,42,18,57,101,245,87,151,153,55,32,238,246,216,57,62,211,211,141, +185,92,97,220,181,83,101,197,106,202,61,224,32,103,50,82,102,189,98,215,94,76,244,116,207,210,156,151,123,162,237,152, +164,233,109,151,54,57,80,253,212,48,67,78,58,147,226,89,229,21,97,164,237,100,106,122,87,197,232,203,119,54,73,179,99, +187,241,245,174,59,190,109,203,80,105,237,245,83,184,148,139,156,169,214,168,116,185,167,234,81,1,171,66,246,221,205,162, +33,86,153,117,40,157,119,167,154,33,44,155,146,227,29,107,29,55,119,184,159,54,205,100,30,248,235,241,120,87,125,51,120, +172,192,15,85,78,175,110,186,97,171,237,242,176,47,25,176,254,70,11,185,28,182,151,248,234,100,38,35,118,146,214,243,231, +247,83,207,223,114,64,96,193,135,143,74,89,104,116,207,236,189,118,194,30,45,188,203,181,243,124,174,89,4,111,238,96,154, +199,110,236,252,126,249,169,185,125,183,199,186,2,95,151,197,165,55,115,110,63,13,156,47,123,224,188,203,22,19,208,49, +115,105,25,140,235,146,163,104,32,102,125,254,204,94,136,157,253,233,209,248,165,66,172,202,102,51,118,18,227,50,111,102, +231,76,118,116,95,62,190,197,134,158,75,58,238,16,146,253,228,135,16,83,176,140,216,118,210,182,99,143,223,142,61,126,59, +246,120,19,15,190,215,35,177,147,2,219,203,246,249,237,59,137,237,36,109,231,70,0,57,188,10,108,160,234,225,25,86,165,54, +226,80,32,57,58,106,231,243,29,125,125,125,84,33,245,117,153,228,158,60,121,147,169,84,14,41,50,147,227,227,182,147,34, +239,174,100,222,222,150,203,144,185,75,140,22,121,70,17,70,100,142,138,88,33,131,239,200,54,249,177,225,143,39,115,118, +34,75,94,117,34,80,160,116,52,80,93,73,79,100,75,167,6,89,163,24,83,215,46,45,198,162,69,78,21,31,144,162,165,116,128,20, +45,50,37,124,42,213,136,22,135,142,204,148,61,154,77,217,84,147,178,119,39,11,25,119,218,228,241,220,140,237,218,20,72, +149,154,82,155,154,241,84,174,158,102,150,213,144,63,149,85,77,38,102,147,193,167,239,48,249,132,192,2,133,166,226,152, +140,221,105,59,147,130,200,20,242,99,164,239,65,102,13,30,197,133,134,23,168,46,133,97,93,203,111,11,42,93,137,244,122, +188,202,206,173,227,53,228,133,67,89,156,146,137,244,160,125,88,56,150,159,24,84,1,195,102,156,146,98,24,120,238,22,59, +63,158,117,242,24,100,140,7,175,38,129,131,44,163,238,34,188,26,236,134,228,131,220,158,204,20,108,178,198,146,249,85, +136,72,213,70,27,33,0,203,229,184,53,80,197,152,104,209,80,218,177,17,41,50,145,167,160,82,18,217,109,136,132,240,24,118, +226,45,252,144,202,187,171,247,167,168,122,122,90,58,5,184,81,181,151,165,201,155,118,82,246,196,21,187,169,34,93,214,67, +95,218,81,77,170,72,231,215,78,140,37,11,121,151,183,38,157,23,227,64,102,58,143,126,186,60,151,75,89,179,47,173,246,111, +242,236,205,166,17,7,25,217,79,15,95,106,228,113,146,251,49,165,142,125,104,117,114,116,204,78,201,233,220,140,85,74,33, +110,44,133,97,5,146,165,96,66,2,175,16,146,47,5,143,195,71,67,119,10,104,36,30,171,14,187,24,143,26,104,235,178,153,76, +246,144,157,218,98,167,210,57,212,36,173,83,169,68,86,58,80,195,76,86,53,126,89,44,187,178,134,232,89,116,85,31,199,219, +61,120,44,20,207,69,228,27,47,206,48,215,220,53,73,55,73,161,162,38,43,242,136,72,245,230,16,233,88,193,84,153,83,97,176, +94,77,91,205,187,12,178,148,9,43,150,11,233,185,2,222,157,71,188,86,225,161,218,147,72,239,183,121,115,42,96,90,147,149, +81,71,141,121,30,152,121,151,135,139,236,72,169,247,220,113,42,22,195,121,30,139,201,84,177,22,75,164,69,96,200,251,28, +69,74,150,205,57,140,67,206,61,76,70,126,60,147,118,33,220,100,14,115,14,225,22,242,20,144,82,132,116,101,73,151,125,8, +230,69,0,169,163,194,204,23,118,237,71,13,126,200,188,216,2,41,228,142,149,111,57,213,60,249,238,93,167,100,44,219,102, +194,48,150,47,194,74,164,183,150,191,204,227,142,165,17,39,252,217,209,71,126,23,93,197,48,109,194,242,157,82,85,156,186, +89,185,31,83,133,91,182,28,235,248,57,144,223,157,205,237,183,83,151,151,69,160,23,59,139,104,129,89,112,68,36,87,28,76, +102,58,138,43,193,56,40,150,137,113,40,135,232,39,109,162,143,142,104,223,103,228,13,211,81,254,156,92,54,64,71,120,234, +30,205,56,199,190,203,190,195,158,198,231,109,227,159,216,188,117,222,240,64,47,255,71,159,215,224,112,98,249,96,186,117, +100,112,120,240,154,97,109,188,121,109,219,57,170,243,143,220,201,30,98,31,96,207,176,111,178,71,216,25,246,65,230,13, +107,127,209,250,39,38,14,107,199,175,59,35,10,247,14,12,14,177,72,37,89,236,5,228,210,106,115,228,163,236,239,217,9,246, +3,225,253,138,238,249,36,107,30,28,184,100,159,174,221,194,22,49,166,235,31,98,108,249,9,221,124,144,177,207,78,232,236, +80,253,9,221,251,19,86,159,214,14,246,27,204,240,106,225,126,195,28,94,48,180,96,80,55,62,203,172,129,75,12,163,197,208, +12,189,85,167,124,211,0,205,243,79,85,255,143,236,139,236,126,118,43,94,210,59,135,85,69,209,230,94,173,162,191,141,126, +141,94,111,92,126,139,214,219,163,237,104,214,42,14,61,62,113,61,107,180,60,204,109,239,127,154,53,84,178,250,208,138,95, +177,250,106,131,241,42,195,56,52,13,226,218,220,19,95,190,230,71,125,90,203,243,6,105,187,250,207,117,124,76,179,250,233, +32,187,131,121,155,81,89,109,143,182,191,249,229,182,143,172,68,15,40,95,211,218,98,144,208,58,91,233,78,62,202,189,186, +245,37,22,111,211,67,167,25,27,234,213,190,77,61,134,165,93,218,108,90,31,56,172,87,125,131,49,86,95,117,78,15,159,69, +159,245,202,207,49,182,224,156,238,223,87,255,65,189,226,83,172,126,167,238,187,166,101,167,30,188,135,181,12,233,218, +179,172,62,206,44,235,240,3,90,104,135,97,173,68,215,125,70,176,85,15,228,155,52,103,163,17,48,53,211,103,6,77,235,125, +172,165,246,162,86,51,64,111,241,169,163,183,197,243,29,241,252,185,166,189,231,51,24,149,54,157,238,195,80,15,78,180, +247,238,213,38,154,7,232,25,158,253,71,157,198,141,229,123,7,90,119,234,230,129,89,3,13,134,73,199,117,246,105,62,117, +154,151,61,207,26,131,154,79,219,233,161,83,172,69,11,32,221,100,68,123,163,203,163,70,180,35,234,213,42,184,1,3,88,84, +170,52,19,74,189,33,138,248,78,177,104,133,44,227,137,174,86,37,68,210,31,101,209,197,209,206,232,122,84,37,51,124,154, +95,214,224,41,86,213,73,26,211,232,100,140,225,255,209,163,158,71,106,53,246,92,45,251,118,236,190,58,50,24,211,144,41, +126,200,115,236,168,231,201,58,131,253,71,29,85,153,94,175,198,34,226,71,228,49,109,218,143,72,155,240,127,164,158,157, +136,189,212,160,69,222,104,96,13,127,152,197,34,63,108,98,145,163,237,190,170,147,179,141,170,183,59,89,213,201,185,172, +234,44,120,18,60,210,197,170,206,116,139,239,148,168,236,187,6,46,139,127,187,197,191,31,40,254,253,86,241,123,11,254,55, +92,252,59,146,226,223,113,241,239,20,138,127,203,101,82,233,239,185,116,75,254,254,141,127,15,195,98,242,119,191,143,240, +239,79,98,210,135,255,254,144,89,165,223,41,106,49,249,94,254,247,95,186,242,231,191,195,243,196,72,252,238,137,255,126, +144,84,89,241,123,71,75,182,149,255,173,217,255,3,11,139,181,139,164,38,0,0}; + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (toString, "toString", "()Ljava/lang/String;") \ @@ -31,7 +192,8 @@ DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer") #undef JNI_CLASS_MEMBERS //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)Lcom/roli/juce/JuceHTTPStream;") \ METHOD (connect, "connect", "()Z") \ METHOD (release, "release", "()V") \ METHOD (read, "read", "([BI)I") \ @@ -40,11 +202,11 @@ DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer") METHOD (isExhausted, "isExhausted", "()Z") \ METHOD (setPosition, "setPosition", "(J)Z") \ -DECLARE_JNI_CLASS (HTTPStream, JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream") +DECLARE_JNI_CLASS_WITH_BYTECODE (HTTPStream, "com/roli/juce/JuceHTTPStream", 16, javaJuceHttpStream, sizeof(javaJuceHttpStream)) #undef JNI_CLASS_MEMBERS //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (close, "close", "()V") \ METHOD (read, "read", "([BII)I") \ @@ -192,8 +354,8 @@ public: const ScopedLock lock (createStreamLock); if (! hasBeenCancelled) - stream = GlobalRef (LocalRef (env->CallStaticObjectMethod (JuceAppActivity, - JuceAppActivity.createHTTPStream, + stream = GlobalRef (LocalRef (env->CallStaticObjectMethod (HTTPStream, + HTTPStream.createHTTPStream, javaString (address).get(), (jboolean) isPost, postDataArray, diff --git a/modules/juce_core/native/juce_android_RuntimePermissions.cpp b/modules/juce_core/native/juce_android_RuntimePermissions.cpp index 6158271d64..8d8fa14638 100644 --- a/modules/juce_core/native/juce_android_RuntimePermissions.cpp +++ b/modules/juce_core/native/juce_android_RuntimePermissions.cpp @@ -23,33 +23,185 @@ namespace juce { -static void handleAndroidCallback (bool permissionWasGranted, RuntimePermissions::Callback* callbackPtr) +//============================================================================== +static String jucePermissionToAndroidPermission (RuntimePermissions::PermissionID permission) { - if (callbackPtr == nullptr) + switch (permission) { - // got a nullptr passed in from java! this should never happen... - jassertfalse; - return; + case RuntimePermissions::recordAudio: return "android.permission.RECORD_AUDIO"; + case RuntimePermissions::bluetoothMidi: return "android.permission.ACCESS_COARSE_LOCATION"; + case RuntimePermissions::readExternalStorage: return "android.permission.READ_EXTERNAL_STORAGE"; + case RuntimePermissions::writeExternalStorage: return "android.permission.WRITE_EXTERNAL_STORAGE"; + case RuntimePermissions::camera: return "android.permission.CAMERA"; } - std::unique_ptr uptr (callbackPtr); - - if (RuntimePermissions::Callback callbackObj = *uptr) - callbackObj (permissionWasGranted); + // invalid permission + jassertfalse; + return {}; } -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, - androidRuntimePermissionsCallback, - void, (JNIEnv* env, jobject, jboolean permissionsGranted, jlong callbackPtr)) +static RuntimePermissions::PermissionID androidPermissionToJucePermission (const String& permission) { - setEnv (env); - handleAndroidCallback (permissionsGranted != 0, - reinterpret_cast (callbackPtr)); + if (permission == "android.permission.RECORD_AUDIO") return RuntimePermissions::recordAudio; + else if (permission == "android.permission.ACCESS_COARSE_LOCATION") return RuntimePermissions::bluetoothMidi; + else if (permission == "android.permission.READ_EXTERNAL_STORAGE") return RuntimePermissions::readExternalStorage; + else if (permission == "android.permission.WRITE_EXTERNAL_STORAGE") return RuntimePermissions::writeExternalStorage; + else if (permission == "android.permission.CAMERA") return RuntimePermissions::camera; + + return static_cast (-1); } +//============================================================================== +struct PermissionsRequest +{ + PermissionsRequest() {} + + // using "= default" on the following method triggers an internal compiler error + // in Android NDK 17 + PermissionsRequest (const PermissionsRequest& o) + : callback (o.callback), permission (o.permission) + {} + + PermissionsRequest (PermissionsRequest&& o) + : callback (std::move (o.callback)), permission (o.permission) + { + o.permission = static_cast (-1); + } + + PermissionsRequest (RuntimePermissions::Callback && callbackToUse, + RuntimePermissions::PermissionID permissionToRequest) + : callback (std::move (callbackToUse)), permission (permissionToRequest) + {} + + PermissionsRequest& operator= (const PermissionsRequest & o) + { + callback = o.callback; + permission = o.permission; + return *this; + } + + PermissionsRequest& operator= (PermissionsRequest && o) + { + callback = std::move (o.callback); + permission = o.permission; + return *this; + } + + RuntimePermissions::Callback callback; + RuntimePermissions::PermissionID permission; +}; + +//============================================================================== +struct PermissionsOverlay : FragmentOverlay +{ + PermissionsOverlay (CriticalSection& cs) : overlayGuard (cs) {} + ~PermissionsOverlay() {} + + struct PermissionResult + { + PermissionsRequest request; + bool granted; + }; + + void onStart() override { onRequestPermissionsResult (0, {}, {}); } + + void onRequestPermissionsResult (int /*requestCode*/, + const StringArray& permissions, + const Array& grantResults) override + { + std::vector results; + + { + ScopedLock lock (overlayGuard); + + for (auto it = requests.begin(); it != requests.end();) + { + auto& request = *it; + + if (RuntimePermissions::isGranted (request.permission)) + { + results.push_back ({std::move (request), true}); + it = requests.erase (it); + } + else + { + ++it; + } + } + + auto n = permissions.size(); + + for (int i = 0; i < n; ++i) + { + auto permission = androidPermissionToJucePermission (permissions[i]); + auto granted = (grantResults.getReference (i) == 0); + + for (auto it = requests.begin(); it != requests.end();) + { + auto& request = *it; + + if (request.permission == permission) + { + results.push_back ({std::move (request), granted}); + it = requests.erase (it); + } + else + { + ++it; + } + } + } + } + + for (const auto& result : results) + if (result.request.callback) + result.request.callback (result.granted); + + { + auto* env = getEnv(); + ScopedLock lock (overlayGuard); + + if (requests.size() > 0) + { + auto &request = requests.front(); + + StringArray permissionsArray{ + jucePermissionToAndroidPermission (request.permission)}; + auto jPermissionsArray = juceStringArrayToJava (permissionsArray); + + + auto requestPermissionsMethodID + = env->GetMethodID(AndroidFragment, "requestPermissions", "([Ljava/lang/String;I)V"); + + // this code should only be reached for SDKs >= 23, so this method should be + // be available + jassert(requestPermissionsMethodID != 0); + + env->CallVoidMethod (getNativeHandle(), requestPermissionsMethodID, jPermissionsArray.get (), 0); + } + else + { + getSingleton() = nullptr; + } + } + } + + static std::unique_ptr& getSingleton() + { + static std::unique_ptr instance; + return instance; + } + + CriticalSection& overlayGuard; + std::vector requests; +}; + +//============================================================================== void RuntimePermissions::request (PermissionID permission, Callback callback) { - if (! android.activity.callBooleanMethod (JuceAppActivity.isPermissionDeclaredInManifest, (jint) permission)) + auto requestedPermission = jucePermissionToAndroidPermission (permission); + + if (! isPermissionDeclaredInManifest (requestedPermission)) { // Error! If you want to be able to request this runtime permission, you // also need to declare it in your app's manifest. You can do so via @@ -60,29 +212,50 @@ void RuntimePermissions::request (PermissionID permission, Callback callback) return; } - if (JUCE_ANDROID_API_VERSION < 23) + auto alreadyGranted = isGranted (permission); + + if (alreadyGranted || getAndroidSDKVersion() < 23) { - // There is no runtime permission system on API level below 23. As long as the - // permission is in the manifest (seems to be the case), we can simply ask Android - // if the app has the permission, and then directly call through to the callback. - callback (isGranted (permission)); + callback (alreadyGranted); return; } - // we need to move the callback object to the heap so Java can keep track of the pointer - // and asynchronously pass it back to us (to be called and then deleted) - Callback* callbackPtr = new Callback (std::move (callback)); - android.activity.callVoidMethod (JuceAppActivity.requestRuntimePermission, permission, (jlong) callbackPtr); + PermissionsRequest request (std::move (callback), permission); + + static CriticalSection overlayGuard; + ScopedLock lock (overlayGuard); + + std::unique_ptr& overlay = PermissionsOverlay::getSingleton(); + + bool alreadyOpen = true; + + if (overlay == nullptr) + { + overlay.reset (new PermissionsOverlay (overlayGuard)); + alreadyOpen = false; + } + + overlay->requests.push_back (std::move (request)); + + if (! alreadyOpen) + overlay->open(); } bool RuntimePermissions::isRequired (PermissionID /*permission*/) { - return JUCE_ANDROID_API_VERSION >= 23; + return getAndroidSDKVersion() >= 23; } bool RuntimePermissions::isGranted (PermissionID permission) { - return android.activity.callBooleanMethod (JuceAppActivity.isPermissionGranted, permission); + auto* env = getEnv(); + + auto requestedPermission = jucePermissionToAndroidPermission (permission); + int result = env->CallIntMethod (getAppContext().get(), AndroidContext.checkCallingOrSelfPermission, + javaString (requestedPermission).get()); + + + return result == 0 /* PERMISSION_GRANTED */; } } // namespace juce diff --git a/modules/juce_core/native/juce_android_SystemStats.cpp b/modules/juce_core/native/juce_android_SystemStats.cpp index ff2afa64ef..aa031b85cb 100644 --- a/modules/juce_core/native/juce_android_SystemStats.cpp +++ b/modules/juce_core/native/juce_android_SystemStats.cpp @@ -23,350 +23,23 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - STATICMETHOD (newProxyInstance, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;") \ - - DECLARE_JNI_CLASS (JavaProxy, "java/lang/reflect/Proxy") -#undef JNI_CLASS_MEMBERS - -JNIClassBase::JNIClassBase (const char* cp) : classPath (cp), classRef (0) -{ - getClasses().add (this); -} - -JNIClassBase::~JNIClassBase() -{ - getClasses().removeFirstMatchingValue (this); -} - -Array& JNIClassBase::getClasses() -{ - static Array classes; - return classes; -} - -void JNIClassBase::initialise (JNIEnv* env) -{ - classRef = (jclass) env->NewGlobalRef (LocalRef (env->FindClass (classPath))); - jassert (classRef != 0); - - initialiseFields (env); -} - -void JNIClassBase::release (JNIEnv* env) -{ - env->DeleteGlobalRef (classRef); -} - -void JNIClassBase::initialiseAllClasses (JNIEnv* env) -{ - const Array& classes = getClasses(); - for (int i = classes.size(); --i >= 0;) - classes.getUnchecked(i)->initialise (env); -} - -void JNIClassBase::releaseAllClasses (JNIEnv* env) -{ - const Array& classes = getClasses(); - for (int i = classes.size(); --i >= 0;) - classes.getUnchecked(i)->release (env); -} - -jmethodID JNIClassBase::resolveMethod (JNIEnv* env, const char* methodName, const char* params) -{ - jmethodID m = env->GetMethodID (classRef, methodName, params); - jassert (m != 0); - return m; -} - -jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params) -{ - jmethodID m = env->GetStaticMethodID (classRef, methodName, params); - jassert (m != 0); - return m; -} - -jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature) -{ - jfieldID f = env->GetFieldID (classRef, fieldName, signature); - jassert (f != 0); - return f; -} - -jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature) -{ - jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature); - jassert (f != 0); - return f; -} - -//============================================================================== -LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, - const StringArray& interfaceNames, - LocalRef subclass) -{ - auto* env = getEnv(); - - implementer->javaSubClass = GlobalRef (subclass); - - // you need to override at least one interface - jassert (interfaceNames.size() > 0); - - auto classArray = LocalRef (env->NewObjectArray (interfaceNames.size(), JavaClass, nullptr)); - LocalRef classLoader; - - for (auto i = 0; i < interfaceNames.size(); ++i) - { - auto aClass = LocalRef (env->FindClass (interfaceNames[i].toRawUTF8())); - - if (aClass != nullptr) - { - if (i == 0) - classLoader = LocalRef (env->CallObjectMethod (aClass, JavaClass.getClassLoader)); - - env->SetObjectArrayElement ((jobjectArray) classArray.get(), i, aClass); - } - else - { - // interface class not found - jassertfalse; - } - } - - auto invocationHandler = LocalRef (env->CallObjectMethod (android.activity, - JuceAppActivity.createInvocationHandler, - reinterpret_cast (implementer))); - - // CreateJavaInterface() is expected to be called just once for a given implementer - jassert (implementer->invocationHandler == nullptr); - - implementer->invocationHandler = GlobalRef (invocationHandler); - - return LocalRef (env->CallStaticObjectMethod (JavaProxy, JavaProxy.newProxyInstance, - classLoader.get(), classArray.get(), - invocationHandler.get())); -} - -LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, - const StringArray& interfaceNames) -{ - return CreateJavaInterface (implementer, interfaceNames, - LocalRef (getEnv()->NewObject (JavaObject, - JavaObject.constructor))); -} - -LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, - const String& interfaceName) -{ - return CreateJavaInterface (implementer, StringArray (interfaceName)); -} - -AndroidInterfaceImplementer::~AndroidInterfaceImplementer() - -{ - if (invocationHandler != nullptr) - getEnv()->CallVoidMethod (android.activity, - JuceAppActivity.invocationHandlerContextDeleted, - invocationHandler.get()); -} - -jobject AndroidInterfaceImplementer::invoke (jobject /*proxy*/, jobject method, jobjectArray args) -{ - auto* env = getEnv(); - return env->CallObjectMethod (method, JavaMethod.invoke, javaSubClass.get(), args); -} - -jobject juce_invokeImplementer (JNIEnv* env, jlong thisPtr, jobject proxy, jobject method, jobjectArray args) -{ - setEnv (env); - return reinterpret_cast (thisPtr)->invoke (proxy, method, args); -} - -void juce_dispatchDelete (JNIEnv* env, jlong thisPtr) -{ - setEnv (env); - delete reinterpret_cast (thisPtr); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeInvocationHandler), dispatchInvoke, - jobject, (JNIEnv* env, jobject /*object*/, jlong thisPtr, jobject proxy, jobject method, jobjectArray args)) -{ - return juce_invokeImplementer (env, thisPtr, proxy, method, args); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeInvocationHandler), dispatchFinalize, - void, (JNIEnv* env, jobject /*object*/, jlong thisPtr)) -{ - juce_dispatchDelete (env, thisPtr); -} - -//============================================================================== -AppPausedResumedListener::AppPausedResumedListener (Owner& ownerToUse) - : owner (ownerToUse) -{ -} - -jobject AppPausedResumedListener::invoke (jobject proxy, jobject method, jobjectArray args) -{ - auto* env = getEnv(); - - auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); - - int numArgs = args != nullptr ? env->GetArrayLength (args) : 0; - - if (methodName == "appPaused" && numArgs == 0) - { - owner.appPaused(); - return nullptr; - } - - if (methodName == "appResumed" && numArgs == 0) - { - owner.appResumed(); - return nullptr; - } - - return AndroidInterfaceImplementer::invoke (proxy, method, args); -} - -//============================================================================== -JavaVM* androidJNIJavaVM = nullptr; - -class JniEnvThreadHolder -{ -public: - static JniEnvThreadHolder& getInstance() noexcept - { - // You cann only use JNI functions AFTER JNI_OnLoad was called - jassert (androidJNIJavaVM != nullptr); - - try - { - if (instance == nullptr) - instance = new JniEnvThreadHolder; - } - catch (...) - { - jassertfalse; - std::terminate(); - } - - return *instance; - } - - static JNIEnv* getEnv() { return reinterpret_cast (pthread_getspecific (getInstance().threadKey)); } - - static void setEnv (JNIEnv* env) - { - // env must not be a nullptr - jassert (env != nullptr); - - #if JUCE_DEBUG - JNIEnv* oldenv = reinterpret_cast (pthread_getspecific (getInstance().threadKey)); - - // This thread is already attached to the JavaVM and you trying to attach - // it to a different instance of the VM. - jassert (oldenv == nullptr || oldenv == env); - #endif - - pthread_setspecific (getInstance().threadKey, env); - } - -private: - pthread_key_t threadKey; - - static void threadDetach (void* p) - { - if (JNIEnv* env = reinterpret_cast (p)) - { - ignoreUnused (env); - - androidJNIJavaVM->DetachCurrentThread(); - } - } - - JniEnvThreadHolder() - { - pthread_key_create (&threadKey, threadDetach); - } - - static JniEnvThreadHolder* instance; -}; - -JniEnvThreadHolder* JniEnvThreadHolder::instance = nullptr; - -//============================================================================== -JNIEnv* attachAndroidJNI() noexcept -{ - auto* env = JniEnvThreadHolder::getEnv(); - - if (env == nullptr) - { - androidJNIJavaVM->AttachCurrentThread (&env, nullptr); - setEnv (env); - } - - return env; -} - -JNIEnv* getEnv() noexcept -{ - auto* env = JniEnvThreadHolder::getEnv(); - - // You are trying to use a JUCE function on a thread that was not created by JUCE. - // You need to first call setEnv on this thread before using JUCE - jassert (env != nullptr); - - return env; -} - -void setEnv (JNIEnv* env) noexcept { JniEnvThreadHolder::setEnv (env); } - -extern "C" jint JNI_OnLoad (JavaVM* vm, void*) -{ - // Huh? JNI_OnLoad was called two times! - jassert (androidJNIJavaVM == nullptr); - - androidJNIJavaVM = vm; - return JNI_VERSION_1_2; -} - -//============================================================================== -AndroidSystem::AndroidSystem() : screenWidth (0), screenHeight (0), dpi (160) -{ -} - -void AndroidSystem::initialise (JNIEnv* env, jobject act, jstring file, jstring dataDir) -{ - setEnv (env); - - screenWidth = screenHeight = 0; - dpi = 160; - JNIClassBase::initialiseAllClasses (env); - - activity = GlobalRef (act); - appFile = juceString (env, file); - appDataDir = juceString (env, dataDir); -} - -void AndroidSystem::shutdown (JNIEnv* env) -{ - activity.clear(); - - JNIClassBase::releaseAllClasses (env); -} - -AndroidSystem android; - //============================================================================== namespace AndroidStatsHelpers { - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getProperty, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;") DECLARE_JNI_CLASS (SystemClass, "java/lang/System") #undef JNI_CLASS_MEMBERS + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + STATICMETHOD (getDefault, "getDefault", "()Ljava/util/Locale;") \ + METHOD (getCountry, "getCountry", "()Ljava/lang/String;") \ + METHOD (getLanguage, "getLanguage", "()Ljava/lang/String;") + + DECLARE_JNI_CLASS (JavaLocale, "java/util/Locale") + #undef JNI_CLASS_MEMBERS + static inline String getSystemProperty (const String& name) { return juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (SystemClass, @@ -376,19 +49,19 @@ namespace AndroidStatsHelpers static inline String getLocaleValue (bool isRegion) { - return juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (JuceAppActivity, - JuceAppActivity.getLocaleValue, - isRegion))); - } + auto* env = getEnv(); + LocalRef locale (env->CallStaticObjectMethod (JavaLocale, JavaLocale.getDefault)); - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) - DECLARE_JNI_CLASS (BuildClass, "android/os/Build") - #undef JNI_CLASS_MEMBERS + auto stringResult = isRegion ? env->CallObjectMethod (locale.get(), JavaLocale.getCountry) + : env->CallObjectMethod (locale.get(), JavaLocale.getLanguage); + + return juceString (LocalRef ((jstring) stringResult)); + } static inline String getAndroidOsBuildValue (const char* fieldName) { return juceString (LocalRef ((jstring) getEnv()->GetStaticObjectField ( - BuildClass, getEnv()->GetStaticFieldID (BuildClass, fieldName, "Ljava/lang/String;")))); + AndroidBuild, getEnv()->GetStaticFieldID (AndroidBuild, fieldName, "Ljava/lang/String;")))); } } diff --git a/modules/juce_core/native/juce_android_Threads.cpp b/modules/juce_core/native/juce_android_Threads.cpp index be8780d232..ec1135b163 100644 --- a/modules/juce_core/native/juce_android_Threads.cpp +++ b/modules/juce_core/native/juce_android_Threads.cpp @@ -28,6 +28,322 @@ namespace juce live in juce_posix_SharedCode.h! */ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + FIELD (activityInfo, "activityInfo", "Landroid/content/pm/ActivityInfo;") + +DECLARE_JNI_CLASS (AndroidResolveInfo, "android/content/pm/ResolveInfo") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +JavaVM* androidJNIJavaVM = nullptr; +jobject androidApkContext = nullptr; + +//============================================================================== +JNIEnv* getEnv() noexcept +{ + if (androidJNIJavaVM != nullptr) + { + JNIEnv* env; + androidJNIJavaVM->AttachCurrentThread (&env, nullptr); + + return env; + } + + // You did not call Thread::initialiseJUCE which must be called at least once in your apk + // before using any JUCE APIs. The Projucer will automatically generate java code + // which will invoke Thread::initialiseJUCE for you. + jassertfalse; + return nullptr; +} + +void JNICALL juce_JavainitialiseJUCE (JNIEnv* env, jobject /*jclass*/, jobject context) +{ + Thread::initialiseJUCE (env, context); +} + +extern "C" jint JNIEXPORT JNI_OnLoad (JavaVM* vm, void*) +{ + // Huh? JNI_OnLoad was called two times! + jassert (androidJNIJavaVM == nullptr); + + androidJNIJavaVM = vm; + + auto* env = getEnv(); + + // register the initialisation function + auto juceJavaClass = env->FindClass("com/roli/juce/Java"); + + if (juceJavaClass != nullptr) + { + JNINativeMethod method {"initialiseJUCE", "(Landroid/content/Context;)V", + reinterpret_cast (juce_JavainitialiseJUCE)}; + + auto status = env->RegisterNatives (juceJavaClass, &method, 1); + jassert (status == 0); + } + else + { + // com.roli.juce.Java class not found. Apparently this project is a library + // or was not generated by the Projucer. That's ok, the user will have to + // call Thread::initialiseJUCE manually + env->ExceptionClear(); + } + + JNIClassBase::initialiseAllClasses (env); + + return JNI_VERSION_1_2; +} + +//============================================================================== +class JuceActivityWatcher : public ActivityLifecycleCallbacks +{ +public: + JuceActivityWatcher() + { + LocalRef appContext (getAppContext()); + + if (appContext != nullptr) + { + auto* env = getEnv(); + + myself = GlobalRef (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks")); + env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, myself.get()); + } + + checkActivityIsMain (androidApkContext); + } + + ~JuceActivityWatcher() + { + LocalRef appContext (getAppContext()); + + if (appContext != nullptr && myself != nullptr) + { + auto* env = getEnv(); + + env->CallVoidMethod (appContext.get(), AndroidApplication.unregisterActivityLifecycleCallbacks, myself.get()); + clear(); + myself.clear(); + } + } + + void onActivityStarted (jobject activity) override + { + auto* env = getEnv(); + + checkActivityIsMain (activity); + + ScopedLock lock (currentActivityLock); + + if (currentActivity != nullptr) + { + // see Clarification June 2001 in JNI reference for why this is + // necessary + LocalRef localStorage (env->NewLocalRef (currentActivity)); + + if (env->IsSameObject (localStorage.get(), activity) != 0) + return; + + env->DeleteWeakGlobalRef (currentActivity); + currentActivity = nullptr; + } + + if (activity != nullptr) + currentActivity = env->NewWeakGlobalRef (activity); + } + + void onActivityStopped (jobject activity) override + { + auto* env = getEnv(); + + ScopedLock lock (currentActivityLock); + + if (currentActivity != nullptr) + { + // important that the comparison happens in this order + // to avoid race condition where the weak reference becomes null + // just after the first check + if (env->IsSameObject (currentActivity, activity) != 0 + || env->IsSameObject (currentActivity, nullptr) != 0) + { + env->DeleteWeakGlobalRef (currentActivity); + currentActivity = nullptr; + } + } + } + + LocalRef getCurrent() + { + ScopedLock lock (currentActivityLock); + return LocalRef (getEnv()->NewLocalRef (currentActivity)); + } + + LocalRef getMain() + { + ScopedLock lock (currentActivityLock); + return LocalRef (getEnv()->NewLocalRef (mainActivity)); + } + + static JuceActivityWatcher& getInstance() + { + static JuceActivityWatcher activityWatcher; + return activityWatcher; + } + +private: + void checkActivityIsMain (jobject context) + { + auto* env = getEnv(); + + ScopedLock lock (currentActivityLock); + + if (mainActivity != nullptr) + { + if (env->IsSameObject (mainActivity, nullptr) != 0) + { + env->DeleteWeakGlobalRef (mainActivity); + mainActivity = nullptr; + } + } + + if (mainActivity == nullptr) + { + LocalRef appContext (getAppContext()); + auto mainActivityPath = getMainActivityClassPath(); + + if (mainActivityPath.isNotEmpty()) + { + auto clasz = env->GetObjectClass (context); + auto activityPath = juceString (LocalRef ((jstring) env->CallObjectMethod (clasz, JavaClass.getName))); + + // This may be problematic for apps which use several activities with the same type. We just + // assume that the very first activity of this type is the main one + if (activityPath == mainActivityPath) + mainActivity = env->NewWeakGlobalRef (context); + } + } + } + + static String getMainActivityClassPath() + { + static String mainActivityClassPath; + + if (mainActivityClassPath.isEmpty()) + { + LocalRef appContext (getAppContext()); + + if (appContext != nullptr) + { + auto* env = getEnv(); + + LocalRef pkgManager (env->CallObjectMethod (appContext.get(), AndroidContext.getPackageManager)); + LocalRef pkgName ((jstring) env->CallObjectMethod (appContext.get(), AndroidContext.getPackageName)); + + LocalRef intent (env->NewObject (AndroidIntent, AndroidIntent.constructWithString, + javaString ("android.intent.action.MAIN").get())); + + intent = LocalRef (env->CallObjectMethod (intent.get(), + AndroidIntent.setPackage, + pkgName.get())); + + LocalRef resolveInfo (env->CallObjectMethod (pkgManager.get(), AndroidPackageManager.resolveActivity, intent.get(), 0)); + + if (resolveInfo != nullptr) + { + LocalRef activityInfo (env->GetObjectField (resolveInfo.get(), AndroidResolveInfo.activityInfo)); + LocalRef jName ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.name)); + LocalRef jPackage ((jstring) env->GetObjectField (activityInfo.get(), AndroidPackageItemInfo.packageName)); + + mainActivityClassPath = juceString (jName); + } + } + } + + return mainActivityClassPath; + } + + GlobalRef myself; + CriticalSection currentActivityLock; + jweak currentActivity = nullptr; + jweak mainActivity = nullptr; +}; + +//============================================================================== +#if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID +void juce_juceEventsAndroidStartApp(); +#endif + +void Thread::initialiseJUCE (void* jniEnv, void* context) +{ + static CriticalSection cs; + ScopedLock lock (cs); + + // jniEnv and context should not be null! + jassert (jniEnv != nullptr && context != nullptr); + + auto* env = static_cast (jniEnv); + + if (androidJNIJavaVM == nullptr) + { + JavaVM* javaVM = nullptr; + + auto status = env->GetJavaVM (&javaVM); + jassert (status == 0 && javaVM != nullptr); + + androidJNIJavaVM = javaVM; + } + + static bool firstCall = true; + + if (firstCall) + { + firstCall = false; + + // if we ever support unloading then this should probably be a weak reference + androidApkContext = env->NewGlobalRef (static_cast (context)); + JuceActivityWatcher::getInstance(); + + #if JUCE_MODULE_AVAILABLE_juce_events && JUCE_ANDROID + juce_juceEventsAndroidStartApp(); + #endif + } +} + +//============================================================================== +LocalRef getAppContext() noexcept +{ + auto* env = getEnv(); + auto context = androidApkContext; + + // You did not call Thread::initialiseJUCE which must be called at least once in your apk + // before using any JUCE APIs. The Projucer will automatically generate java code + // which will invoke Thread::initialiseJUCE for you. + jassert (env != nullptr && context != nullptr); + + if (context == nullptr) + return LocalRef(); + + if (env->IsInstanceOf (context, AndroidApplication) != 0) + return LocalRef (env->NewLocalRef (context)); + + LocalRef applicationContext (env->CallObjectMethod (context, AndroidContext.getApplicationContext)); + + if (applicationContext == nullptr) + return LocalRef (env->NewLocalRef (context)); + + return applicationContext; +} + +LocalRef getCurrentActivity() noexcept +{ + return JuceActivityWatcher::getInstance().getCurrent(); +} + +LocalRef getMainActivity() noexcept +{ + return JuceActivityWatcher::getInstance().getMain(); +} + //============================================================================== // sets the process to 0=low priority, 1=normal, 2=high, 3=realtime JUCE_API void JUCE_CALLTYPE Process::setPriority (ProcessPriority prior) @@ -74,4 +390,6 @@ JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} + + } // namespace juce diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h index 3786b2caaa..d833c89639 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h @@ -640,9 +640,6 @@ MemoryMappedFile::~MemoryMappedFile() File juce_getExecutableFile(); File juce_getExecutableFile() { - #if JUCE_ANDROID - return File (android.appFile); - #else struct DLAddrReader { static String getFilename() @@ -657,7 +654,6 @@ File juce_getExecutableFile() static String filename = DLAddrReader::getFilename(); return File::getCurrentWorkingDirectory().getChildFile (filename); - #endif } //============================================================================== @@ -891,28 +887,27 @@ void JUCE_API juce_threadEntryPoint (void*); extern JavaVM* androidJNIJavaVM; #endif -static void* threadEntryProc (void* userData) +extern "C" void* threadEntryProc (void*); +extern "C" void* threadEntryProc (void* userData) { - #if JUCE_ANDROID + auto* myself = static_cast (userData); - if (androidJNIJavaVM != nullptr) - { - JNIEnv* env; - androidJNIJavaVM->AttachCurrentThread (&env, nullptr); - setEnv (env); - } - else + JUCE_AUTORELEASEPOOL { - // JNI_OnLoad was not called - make sure you load the JUCE shared library - // using System.load inside of Java - jassertfalse; + juce_threadEntryPoint (myself); } - #endif - JUCE_AUTORELEASEPOOL + #if JUCE_ANDROID + if (androidJNIJavaVM != nullptr) { - juce_threadEntryPoint (userData); + void* env = nullptr; + androidJNIJavaVM->GetEnv(&env, JNI_VERSION_1_2); + + // only detach if we have actually been attached + if (env != nullptr) + androidJNIJavaVM->DetachCurrentThread(); } + #endif return nullptr; } @@ -955,6 +950,7 @@ void Thread::launchThread() pthread_attr_setstacksize (attrPtr, threadStackSize); } + if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0) { pthread_detach (handle); @@ -1335,15 +1331,7 @@ private: static void* timerThread (void* param) { - #if JUCE_ANDROID - // JNI_OnLoad was not called - make sure you load the JUCE shared library - // using System.load inside of Java - jassert (androidJNIJavaVM != nullptr); - - JNIEnv* env; - androidJNIJavaVM->AttachCurrentThread (&env, nullptr); - setEnv (env); - #else + #if ! JUCE_ANDROID int dummy; pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &dummy); #endif diff --git a/modules/juce_core/threads/juce_Thread.h b/modules/juce_core/threads/juce_Thread.h index 158860c07e..1f0cedd274 100644 --- a/modules/juce_core/threads/juce_Thread.h +++ b/modules/juce_core/threads/juce_Thread.h @@ -324,6 +324,47 @@ public: */ static void JUCE_CALLTYPE setCurrentThreadName (const String& newThreadName); + #if JUCE_ANDROID || defined (DOXYGEN) + //============================================================================== + /** Initialises the JUCE subsystem for projects not created by the Projucer + + On Android, JUCE needs to be initialised once before it is used. The Projucer + will automatically generate the necessary java code to do this. However, if + you are using JUCE without the Projucer or are creating a library made with + JUCE intended for use in non-JUCE apks, then you must call this method + manually once on apk startup. + + You can call this method from C++ or directly from java by calling the + following java method: + + @code + com.roli.juce.Java.initialiseJUCE (myContext); + @endcode + + Note that the above java method is only available in Android Studio projects + created by the Projucer. If you need to call this from another type of project + then you need to add the following java file to + your project: + + @code + package com.roli.juce; + + public class Java + { + static { System.loadLibrary ("juce_jni"); } + public native static void initialiseJUCE (Context context); + } + @endcode + + @param jniEnv this is a pointer to JNI's JNIEnv variable. Any callback + from Java into C++ will have this passed in as it's first + parameter. + @param jContext this is a jobject referring to your app/service/receiver/ + provider's Context. JUCE needs this for many of it's internal + functions. + */ + static void initialiseJUCE (void* jniEnv, void* jContext); + #endif private: //============================================================================== diff --git a/modules/juce_events/messages/juce_Initialisation.h b/modules/juce_events/messages/juce_Initialisation.h index 9fa1ef31f4..c5cb34db84 100644 --- a/modules/juce_events/messages/juce_Initialisation.h +++ b/modules/juce_events/messages/juce_Initialisation.h @@ -116,7 +116,7 @@ public: #elif JUCE_ANDROID #define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ - juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } + extern "C" juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } #define JUCE_MAIN_FUNCTION_DEFINITION diff --git a/modules/juce_events/native/juce_android_Messaging.cpp b/modules/juce_events/native/juce_android_Messaging.cpp index a43d97a9d9..63f5bcabd6 100644 --- a/modules/juce_events/native/juce_android_Messaging.cpp +++ b/modules/juce_events/native/juce_android_Messaging.cpp @@ -50,7 +50,7 @@ namespace Android struct Handler { - Handler() : nativeHandler (getEnv()->NewObject (AndroidHandler, AndroidHandler.constructor)) {} + Handler() : nativeHandler (LocalRef (getEnv()->NewObject (AndroidHandler, AndroidHandler.constructor))) {} ~Handler() { clearSingletonInstance(); } JUCE_DECLARE_SINGLETON (Handler, false) @@ -72,7 +72,7 @@ struct AndroidMessageQueue : private Android::Runnable JUCE_DECLARE_SINGLETON_SINGLETHREADED (AndroidMessageQueue, true) AndroidMessageQueue() - : self (CreateJavaInterface (this, "java/lang/Runnable").get()) + : self (CreateJavaInterface (this, "java/lang/Runnable")) { } @@ -131,6 +131,7 @@ bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* cons { return AndroidMessageQueue::getInstance()->post (message); } + //============================================================================== void MessageManager::broadcastMessage (const String&) { @@ -149,18 +150,26 @@ void MessageManager::stopDispatchLoop() void messageCallback() override { auto* env = getEnv(); + LocalRef activity (getCurrentActivity()); + + if (activity != nullptr) + { + jmethodID quitMethod = env->GetMethodID (AndroidActivity, "finishAndRemoveTask", "()V"); - jmethodID quitMethod = env->GetMethodID (JuceAppActivity, "finishAndRemoveTask", "()V"); + if (quitMethod != 0) + { + env->CallVoidMethod (activity.get(), quitMethod); + return; + } - if (quitMethod != 0) + quitMethod = env->GetMethodID (AndroidActivity, "finish", "()V"); + jassert (quitMethod != 0); + env->CallVoidMethod (activity.get(), quitMethod); + } + else { - env->CallVoidMethod (android.activity, quitMethod); - return; + jassertfalse; } - - quitMethod = env->GetMethodID (JuceAppActivity, "finish", "()V"); - jassert (quitMethod != 0); - env->CallVoidMethod (android.activity, quitMethod); } }; @@ -168,4 +177,134 @@ void MessageManager::stopDispatchLoop() quitMessagePosted = true; } +//============================================================================== +class JuceAppLifecycle : public ActivityLifecycleCallbacks +{ +public: + JuceAppLifecycle (juce::JUCEApplicationBase* (*initSymbolAddr)()) + : createApplicationSymbol (initSymbolAddr) + { + LocalRef appContext (getAppContext()); + + if (appContext != nullptr) + { + auto* env = getEnv(); + + myself = GlobalRef (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks")); + env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, myself.get()); + } + } + + ~JuceAppLifecycle() + { + LocalRef appContext (getAppContext()); + + if (appContext != nullptr && myself != nullptr) + { + auto* env = getEnv(); + + clear(); + env->CallVoidMethod (appContext.get(), AndroidApplication.unregisterActivityLifecycleCallbacks, myself.get()); + myself.clear(); + } + } + + void onActivityCreated (jobject, jobject) override + { + checkCreated(); + } + + void onActivityDestroyed (jobject activity) override + { + auto* env = getEnv(); + + // if the main activity is being destroyed, only then tear-down JUCE + if (env->IsSameObject (getMainActivity().get(), activity) != 0) + { + JUCEApplicationBase::appWillTerminateByForce(); + JNIClassBase::releaseAllClasses (env); + + jclass systemClass = (jclass) env->FindClass ("java/lang/System"); + jmethodID exitMethod = env->GetStaticMethodID (systemClass, "exit", "(I)V"); + env->CallStaticVoidMethod (systemClass, exitMethod, 0); + } + } + + void onActivityStarted (jobject) override + { + checkCreated(); + } + + void onActivityPaused (jobject) override + { + if (auto* app = JUCEApplicationBase::getInstance()) + app->suspended(); + } + + void onActivityResumed (jobject) override + { + checkInitialised(); + + if (auto* app = JUCEApplicationBase::getInstance()) + app->resumed(); + } + + static JuceAppLifecycle& getInstance (juce::JUCEApplicationBase* (*initSymbolAddr)()) + { + static JuceAppLifecycle juceAppLifecycle (initSymbolAddr); + return juceAppLifecycle; + } + +private: + void checkCreated() + { + if (JUCEApplicationBase::getInstance() == nullptr) + { + DBG (SystemStats::getJUCEVersion()); + + JUCEApplicationBase::createInstance = createApplicationSymbol; + + initialiseJuce_GUI(); + + if (! JUCEApplicationBase::createInstance()) + jassertfalse; // you must supply an application object for an android app! + + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + } + } + + void checkInitialised() + { + checkCreated(); + + if (! hasBeenInitialised) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + hasBeenInitialised = app->initialiseApp(); + + if (! hasBeenInitialised) + exit (app->shutdownApp()); + } + } + } + + GlobalRef myself; + juce::JUCEApplicationBase* (*createApplicationSymbol)(); + bool hasBeenInitialised = false; +}; + +//============================================================================== +File juce_getExecutableFile(); + +void juce_juceEventsAndroidStartApp() +{ + auto dllPath = juce_getExecutableFile().getFullPathName(); + auto addr = reinterpret_cast (DynamicLibrary (dllPath) + .getFunction ("juce_CreateApplication")); + + if (addr != nullptr) + JuceAppLifecycle::getInstance (addr); +} + } // namespace juce diff --git a/modules/juce_graphics/native/juce_android_Fonts.cpp b/modules/juce_graphics/native/juce_android_Fonts.cpp index 4e0c750d2d..cf3e36146f 100644 --- a/modules/juce_graphics/native/juce_android_Fonts.cpp +++ b/modules/juce_graphics/native/juce_android_Fonts.cpp @@ -94,11 +94,37 @@ bool TextLayout::createNativeLayout (const AttributedString&) #else //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (create, "create", "(Ljava/lang/String;I)Landroid/graphics/Typeface;") \ STATICMETHOD (createFromFile, "createFromFile", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ + STATICMETHOD (createFromAsset, "createFromAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;)Landroid/graphics/Typeface;") -DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface") + DECLARE_JNI_CLASS (TypefaceClass, "android/graphics/Typeface") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "()V") \ + METHOD (computeBounds, "computeBounds", "(Landroid/graphics/RectF;Z)V") + + DECLARE_JNI_CLASS (AndroidPath, "android/graphics/Path") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "()V") \ + FIELD (left, "left", "F") \ + FIELD (right, "right", "F") \ + FIELD (top, "top", "F") \ + FIELD (bottom, "bottom", "F") \ + METHOD (roundOut, "roundOut", "(Landroid/graphics/Rect;)V") + +DECLARE_JNI_CLASS (AndroidRectF, "android/graphics/RectF") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + STATICMETHOD (getInstance, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;") \ + METHOD (update, "update", "([B)V") \ + METHOD (digest, "digest", "()[B") +DECLARE_JNI_CLASS (JavaMessageDigest, "java/security/MessageDigest") #undef JNI_CLASS_MEMBERS //============================================================================== @@ -136,8 +162,7 @@ public: JNIEnv* const env = getEnv(); // First check whether there's an embedded asset with this font name: - typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromAsset, - javaString ("fonts/" + name).get())); + typeface = GlobalRef (getTypefaceFromAsset (name)); if (typeface.get() == nullptr) { @@ -150,12 +175,12 @@ public: fontFile = findFontFile (name, isBold, isItalic); if (fontFile.exists()) - typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, - javaString (fontFile.getFullPathName()).get())); + typeface = GlobalRef (LocalRef(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, + javaString (fontFile.getFullPathName()).get()))); else - typeface = GlobalRef (env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create, - javaString (getName()).get(), - (isBold ? 1 : 0) + (isItalic ? 2 : 0))); + typeface = GlobalRef (LocalRef(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create, + javaString (getName()).get(), + (isBold ? 1 : 0) + (isItalic ? 2 : 0)))); } initialise (env); @@ -164,23 +189,24 @@ public: AndroidTypeface (const void* data, size_t size) : Typeface (String (static_cast (reinterpret_cast (data))), String()) { - JNIEnv* const env = getEnv(); + auto* env = getEnv(); + auto cacheFile = getCacheFileForData (data, size); - LocalRef bytes (env->NewByteArray ((jsize) size)); - env->SetByteArrayRegion (bytes, 0, (jsize) size, (const jbyte*) data); - - typeface = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.getTypeFaceFromByteArray, bytes.get())); + typeface = GlobalRef (LocalRef(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, + javaString (cacheFile.getFullPathName()).get()))); initialise (env); } void initialise (JNIEnv* const env) { - rect = GlobalRef (env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0)); + rect = GlobalRef (LocalRef(env->NewObject (AndroidRect, AndroidRect.constructor, 0, 0, 0, 0))); paint = GlobalRef (GraphicsHelpers::createPaint (Graphics::highResamplingQuality)); const LocalRef ignored (paint.callObjectMethod (AndroidPaint.setTypeface, typeface.get())); + charArray = GlobalRef (LocalRef((jobject) env->NewCharArray (2))); + paint.callVoidMethod (AndroidPaint.setTextSize, referenceFontSize); const float fullAscent = std::abs (paint.callFloatMethod (AndroidPaint.ascent)); @@ -291,20 +317,56 @@ public: #else jchar ch1 = glyphNumber, ch2 = 0; #endif + Rectangle bounds; + auto* env = getEnv(); + + { + LocalRef matrix (GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t))); + + jboolean isCopy; + auto* buffer = env->GetCharArrayElements ((jcharArray) charArray.get(), &isCopy); + + buffer[0] = ch1; buffer[1] = ch2; + env->ReleaseCharArrayElements ((jcharArray) charArray.get(), buffer, 0); + + LocalRef path (env->NewObject (AndroidPath, AndroidPath.constructor)); + LocalRef boundsF (env->NewObject (AndroidRectF, AndroidRectF.constructor)); - JNIEnv* env = getEnv(); - jobject matrix = GraphicsHelpers::createMatrix (env, AffineTransform::scale (referenceFontToUnits).followedBy (t)); - jintArray maskData = (jintArray) android.activity.callObjectMethod (JuceAppActivity.renderGlyph, ch1, ch2, paint.get(), matrix, rect.get()); + env->CallVoidMethod (paint.get(), AndroidPaint.getCharsPath, charArray.get(), 0, (ch2 != 0 ? 2 : 1), 0.0f, 0.0f, path.get()); - env->DeleteLocalRef (matrix); + env->CallVoidMethod (path.get(), AndroidPath.computeBounds, boundsF.get(), 1); - const int left = env->GetIntField (rect.get(), AndroidRect.left); - const int top = env->GetIntField (rect.get(), AndroidRect.top); - const int right = env->GetIntField (rect.get(), AndroidRect.right); - const int bottom = env->GetIntField (rect.get(), AndroidRect.bottom); + env->CallBooleanMethod (matrix.get(), AndroidMatrix.mapRect, boundsF.get()); - const Rectangle bounds (left, top, right - left, bottom - top); + env->CallVoidMethod (boundsF.get(), AndroidRectF.roundOut, rect.get()); + + bounds = Rectangle::leftTopRightBottom (env->GetIntField (rect.get(), AndroidRect.left) - 1, + env->GetIntField (rect.get(), AndroidRect.top), + env->GetIntField (rect.get(), AndroidRect.right) + 1, + env->GetIntField (rect.get(), AndroidRect.bottom)); + + auto w = bounds.getWidth(); + auto h = jmax (1, bounds.getHeight()); + + LocalRef bitmapConfig (env->CallStaticObjectMethod (AndroidBitmapConfig, AndroidBitmapConfig.valueOf, javaString ("ARGB_8888").get())); + LocalRef bitmap (env->CallStaticObjectMethod (AndroidBitmap, AndroidBitmap.createBitmap, w, h, bitmapConfig.get())); + LocalRef canvas (env->NewObject (AndroidCanvas, AndroidCanvas.create, bitmap.get())); + + env->CallBooleanMethod (matrix.get(), AndroidMatrix.postTranslate, bounds.getX() * -1.0f, bounds.getY() * -1.0f); + env->CallVoidMethod (canvas.get(), AndroidCanvas.setMatrix, matrix.get()); + env->CallVoidMethod (canvas.get(), AndroidCanvas.drawPath, path.get(), paint.get()); + + int requiredRenderArraySize = w * h; + if (requiredRenderArraySize > lastCachedRenderArraySize) + { + cachedRenderArray = GlobalRef (LocalRef ((jobject) env->NewIntArray (requiredRenderArraySize))); + lastCachedRenderArraySize = requiredRenderArraySize; + } + + env->CallVoidMethod (bitmap.get(), AndroidBitmap.getPixels, cachedRenderArray.get(), 0, w, 0, 0, w, h); + env->CallVoidMethod (bitmap.get(), AndroidBitmap.recycle); + } EdgeTable* et = nullptr; @@ -312,10 +374,10 @@ public: { et = new EdgeTable (bounds); - jint* const maskDataElements = env->GetIntArrayElements (maskData, 0); + jint* const maskDataElements = env->GetIntArrayElements ((jintArray) cachedRenderArray.get(), 0); const jint* mask = maskDataElements; - for (int y = top; y < bottom; ++y) + for (int y = bounds.getY(); y < bounds.getBottom(); ++y) { #if JUCE_LITTLE_ENDIAN const uint8* const lineBytes = ((const uint8*) mask) + 3; @@ -323,19 +385,19 @@ public: const uint8* const lineBytes = (const uint8*) mask; #endif - et->clipLineToMask (left, y, lineBytes, 4, bounds.getWidth()); + et->clipLineToMask (bounds.getX(), y, lineBytes, 4, bounds.getWidth()); mask += bounds.getWidth(); } - env->ReleaseIntArrayElements (maskData, maskDataElements, 0); + env->ReleaseIntArrayElements ((jintArray) cachedRenderArray.get(), maskDataElements, 0); } - env->DeleteLocalRef (maskData); return et; } - GlobalRef typeface, paint, rect; + GlobalRef typeface, paint, rect, charArray, cachedRenderArray; float ascent, descent, heightToPointsFactor; + int lastCachedRenderArraySize = -1; private: static File findFontFile (const String& family, @@ -373,6 +435,92 @@ private: return File (path + ".ttf"); } + static LocalRef getTypefaceFromAsset (const String& typefaceName) + { + auto* env = getEnv(); + + LocalRef assetManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getAssets)); + + if (assetManager == nullptr) + return LocalRef(); + + auto assetTypeface = env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromAsset, assetManager.get(), + javaString ("fonts/" + typefaceName).get()); + + // this may throw + if (env->ExceptionCheck() != 0) + { + env->ExceptionClear(); + return LocalRef(); + } + + return LocalRef (assetTypeface); + } + + static File getCacheDirectory() + { + static File result = [] () + { + auto appContext = getAppContext(); + + if (appContext != nullptr) + { + auto* env = getEnv(); + + LocalRef cacheFile (env->CallObjectMethod (appContext.get(), AndroidContext.getCacheDir)); + LocalRef jPath ((jstring) env->CallObjectMethod (cacheFile.get(), JavaFile.getAbsolutePath)); + + return File (juceString (env, jPath.get())); + } + + jassertfalse; + return File(); + } (); + + return result; + } + + static HashMap& getInMemoryFontCache() + { + static HashMap cache; + return cache; + } + + static File getCacheFileForData (const void* data, size_t size) + { + static CriticalSection cs; + JNIEnv* const env = getEnv(); + + String key; + { + LocalRef digest (env->CallStaticObjectMethod (JavaMessageDigest, JavaMessageDigest.getInstance, javaString("MD5").get())); + LocalRef bytes(env->NewByteArray(size)); + + jboolean ignore; + auto* jbytes = env->GetByteArrayElements(bytes.get(), &ignore); + memcpy(jbytes, data, size); + env->ReleaseByteArrayElements(bytes.get(), jbytes, 0); + + env->CallVoidMethod(digest.get(), JavaMessageDigest.update, bytes.get()); + LocalRef result((jbyteArray) env->CallObjectMethod(digest.get(), JavaMessageDigest.digest)); + + auto* md5Bytes = env->GetByteArrayElements(result.get(), &ignore); + key = String::toHexString(md5Bytes, env->GetArrayLength(result.get()), 0); + env->ReleaseByteArrayElements(result.get(), md5Bytes, 0); + } + + ScopedLock lock (cs); + auto& mapEntry = getInMemoryFontCache().getReference (key); + + if (mapEntry == File()) + { + mapEntry = getCacheDirectory().getChildFile ("bindata_" + key); + mapEntry.replaceWithData (data, size); + } + + return mapEntry; + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidTypeface) }; diff --git a/modules/juce_graphics/native/juce_android_GraphicsContext.cpp b/modules/juce_graphics/native/juce_android_GraphicsContext.cpp index a53a5a559e..ec3a14fc39 100644 --- a/modules/juce_graphics/native/juce_android_GraphicsContext.cpp +++ b/modules/juce_graphics/native/juce_android_GraphicsContext.cpp @@ -29,7 +29,7 @@ namespace juce namespace GraphicsHelpers { - jobject createPaint (Graphics::ResamplingQuality quality) + LocalRef createPaint (Graphics::ResamplingQuality quality) { jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/ | 4 /*DITHER_FLAG*/ @@ -38,13 +38,12 @@ namespace GraphicsHelpers if (quality > Graphics::lowResamplingQuality) constructorFlags |= 2; /*FILTER_BITMAP_FLAG*/ - return getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags); + return LocalRef(getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags)); } - // - const jobject createMatrix (JNIEnv* env, const AffineTransform& t) + const LocalRef createMatrix (JNIEnv* env, const AffineTransform& t) { - jobject m = env->NewObject (AndroidMatrix, AndroidMatrix.constructor); + auto m = LocalRef(env->NewObject (AndroidMatrix, AndroidMatrix.constructor)); jfloat values[9] = { t.mat00, t.mat01, t.mat02, t.mat10, t.mat11, t.mat12, diff --git a/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h b/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h index 8a0026a7fc..0578ca74f5 100644 --- a/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h +++ b/modules/juce_gui_basics/filebrowser/juce_ContentSharer.h @@ -139,13 +139,6 @@ private: void deleteTemporaryFiles(); void sharingFinished (bool, const String&); - - #if JUCE_ANDROID - friend void* juce_contentSharerOpenFile (void*, void*, void*); - friend void* juce_contentSharerQuery (void*, void*, void*, void*, void*, void*); - friend void* juce_contentSharerGetStreamTypes (void*, void*); - friend void juce_contentSharingCompleted (int); - #endif }; } // namespace juce diff --git a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index b43a3790be..8554b6150a 100644 --- a/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -598,7 +598,7 @@ struct MenuWindow : public Component targetPoint = relativeTo->localPointToGlobal (targetPoint); auto parentArea = Desktop::getInstance().getDisplays().findDisplayForPoint (targetPoint) - #if JUCE_MAC + #if JUCE_MAC || JUCE_ANDROID .userArea; #else .totalArea; // on windows, don't stop the menu overlapping the taskbar diff --git a/modules/juce_gui_basics/native/java/com/roli/juce/ComponentPeerView.java b/modules/juce_gui_basics/native/java/com/roli/juce/ComponentPeerView.java new file mode 100644 index 0000000000..5415f26016 --- /dev/null +++ b/modules/juce_gui_basics/native/java/com/roli/juce/ComponentPeerView.java @@ -0,0 +1,493 @@ +package com.roli.juce; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Bundle; +import android.text.InputType; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.inputmethod.BaseInputConnection; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; + +import java.lang.reflect.Method; + +public final class ComponentPeerView extends ViewGroup + implements View.OnFocusChangeListener, Application.ActivityLifecycleCallbacks +{ + public ComponentPeerView (Context context, boolean opaque_, long host) + { + super (context); + + if (Application.class.isInstance (context)) + { + ((Application) context).registerActivityLifecycleCallbacks (this); + } else + { + ((Application) context.getApplicationContext ()).registerActivityLifecycleCallbacks (this); + } + + this.host = host; + setWillNotDraw (false); + opaque = opaque_; + + setFocusable (true); + setFocusableInTouchMode (true); + setOnFocusChangeListener (this); + + // swap red and blue colours to match internal opengl texture format + ColorMatrix colorMatrix = new ColorMatrix (); + + float[] colorTransform = {0, 0, 1.0f, 0, 0, + 0, 1.0f, 0, 0, 0, + 1.0f, 0, 0, 0, 0, + 0, 0, 0, 1.0f, 0}; + + colorMatrix.set (colorTransform); + paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix)); + + java.lang.reflect.Method method = null; + + try + { + method = getClass ().getMethod ("setLayerType", int.class, Paint.class); + } catch (SecurityException e) + { + } catch (NoSuchMethodException e) + { + } + + if (method != null) + { + try + { + int layerTypeNone = 0; + method.invoke (this, layerTypeNone, null); + } catch (java.lang.IllegalArgumentException e) + { + } catch (java.lang.IllegalAccessException e) + { + } catch (java.lang.reflect.InvocationTargetException e) + { + } + } + } + + public void clear () + { + host = 0; + } + + //============================================================================== + private native void handlePaint (long host, Canvas canvas, Paint paint); + + @Override + public void onDraw (Canvas canvas) + { + if (host == 0) + return; + + handlePaint (host, canvas, paint); + } + + @Override + public boolean isOpaque () + { + return opaque; + } + + private boolean opaque; + private long host; + private Paint paint = new Paint (); + + //============================================================================== + private native void handleMouseDown (long host, int index, float x, float y, long time); + private native void handleMouseDrag (long host, int index, float x, float y, long time); + private native void handleMouseUp (long host, int index, float x, float y, long time); + + @Override + public boolean onTouchEvent (MotionEvent event) + { + if (host == 0) + return false; + + int action = event.getAction (); + long time = event.getEventTime (); + + switch (action & MotionEvent.ACTION_MASK) + { + case MotionEvent.ACTION_DOWN: + handleMouseDown (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time); + return true; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + handleMouseUp (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time); + return true; + + case MotionEvent.ACTION_MOVE: + { + handleMouseDrag (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time); + + int n = event.getPointerCount (); + + if (n > 1) + { + int point[] = new int[2]; + getLocationOnScreen (point); + + for (int i = 1; i < n; ++i) + handleMouseDrag (host, event.getPointerId (i), event.getX (i) + point[0], event.getY (i) + point[1], time); + } + + return true; + } + + case MotionEvent.ACTION_POINTER_UP: + { + int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + + if (i == 0) + { + handleMouseUp (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time); + } else + { + int point[] = new int[2]; + getLocationOnScreen (point); + + handleMouseUp (host, event.getPointerId (i), event.getX (i) + point[0], event.getY (i) + point[1], time); + } + return true; + } + + case MotionEvent.ACTION_POINTER_DOWN: + { + int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + + if (i == 0) + { + handleMouseDown (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time); + } else + { + int point[] = new int[2]; + getLocationOnScreen (point); + + handleMouseDown (host, event.getPointerId (i), event.getX (i) + point[0], event.getY (i) + point[1], time); + } + return true; + } + + default: + break; + } + + return false; + } + + //============================================================================== + private native void handleKeyDown (long host, int keycode, int textchar); + private native void handleKeyUp (long host, int keycode, int textchar); + private native void handleBackButton (long host); + private native void handleKeyboardHidden (long host); + + public void showKeyboard (String type) + { + InputMethodManager imm = (InputMethodManager) getContext ().getSystemService (Context.INPUT_METHOD_SERVICE); + + if (imm != null) + { + if (type.length () > 0) + { + imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); + imm.setInputMethod (getWindowToken (), type); + keyboardDismissListener.startListening (); + } else + { + imm.hideSoftInputFromWindow (getWindowToken (), 0); + keyboardDismissListener.stopListening (); + } + } + } + + public void backButtonPressed () + { + if (host == 0) + return; + + handleBackButton (host); + } + + @Override + public boolean onKeyDown (int keyCode, KeyEvent event) + { + if (host == 0) + return false; + + switch (keyCode) + { + case KeyEvent.KEYCODE_VOLUME_UP: + case KeyEvent.KEYCODE_VOLUME_DOWN: + return super.onKeyDown (keyCode, event); + case KeyEvent.KEYCODE_BACK: + { + ((Activity) getContext ()).onBackPressed (); + return true; + } + + default: + break; + } + + handleKeyDown (host, keyCode, event.getUnicodeChar ()); + return true; + } + + @Override + public boolean onKeyUp (int keyCode, KeyEvent event) + { + if (host == 0) + return false; + + handleKeyUp (host, keyCode, event.getUnicodeChar ()); + return true; + } + + @Override + public boolean onKeyMultiple (int keyCode, int count, KeyEvent event) + { + if (host == 0) + return false; + + if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction () != KeyEvent.ACTION_MULTIPLE) + return super.onKeyMultiple (keyCode, count, event); + + if (event.getCharacters () != null) + { + int utf8Char = event.getCharacters ().codePointAt (0); + handleKeyDown (host, utf8Char, utf8Char); + return true; + } + + return false; + } + + //============================================================================== + private final class KeyboardDismissListener + { + public KeyboardDismissListener (ComponentPeerView viewToUse) + { + view = viewToUse; + } + + private void startListening () + { + view.getViewTreeObserver ().addOnGlobalLayoutListener (viewTreeObserver); + } + + private void stopListening () + { + view.getViewTreeObserver ().removeGlobalOnLayoutListener (viewTreeObserver); + } + + private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener + { + TreeObserver () + { + keyboardShown = false; + } + + @Override + public void onGlobalLayout () + { + Rect r = new Rect (); + + View parentView = getRootView (); + int diff = 0; + + if (parentView == null) + { + getWindowVisibleDisplayFrame (r); + diff = getHeight () - (r.bottom - r.top); + } else + { + parentView.getWindowVisibleDisplayFrame (r); + diff = parentView.getHeight () - (r.bottom - r.top); + } + + // Arbitrary threshold, surely keyboard would take more than 20 pix. + if (diff < 20 && keyboardShown) + { + keyboardShown = false; + handleKeyboardHidden (view.host); + } + + if (!keyboardShown && diff > 20) + keyboardShown = true; + } + + ; + + private boolean keyboardShown; + } + + ; + + private ComponentPeerView view; + private TreeObserver viewTreeObserver = new TreeObserver (); + } + + private KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener (this); + + // this is here to make keyboard entry work on a Galaxy Tab2 10.1 + @Override + public InputConnection onCreateInputConnection (EditorInfo outAttrs) + { + outAttrs.actionLabel = ""; + outAttrs.hintText = ""; + outAttrs.initialCapsMode = 0; + outAttrs.initialSelEnd = outAttrs.initialSelStart = -1; + outAttrs.label = ""; + outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI; + outAttrs.inputType = InputType.TYPE_NULL; + + return new BaseInputConnection (this, false); + } + + //============================================================================== + @Override + protected void onSizeChanged (int w, int h, int oldw, int oldh) + { + super.onSizeChanged (w, h, oldw, oldh); + + if (host != 0) + viewSizeChanged (host); + } + + @Override + protected void onLayout (boolean changed, int left, int top, int right, int bottom) + { + } + + private native void viewSizeChanged (long host); + + @Override + public void onFocusChange (View v, boolean hasFocus) + { + if (host == 0) + return; + + if (v == this) + focusChanged (host, hasFocus); + } + + private native void focusChanged (long host, boolean hasFocus); + + 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); + } + + public boolean containsPoint (int x, int y) + { + return true; //xxx needs to check overlapping views + } + + //============================================================================== + private native void handleAppPaused (long host); + private native void handleAppResumed (long host); + + @Override + public void onActivityPaused (Activity activity) + { + if (host == 0) + return; + + handleAppPaused (host); + } + + @Override + public void onActivityStopped (Activity activity) + { + + } + + @Override + public void onActivitySaveInstanceState (Activity activity, Bundle bundle) + { + + } + + @Override + public void onActivityDestroyed (Activity activity) + { + + } + + @Override + public void onActivityCreated (Activity activity, Bundle bundle) + { + + } + + @Override + public void onActivityStarted (Activity activity) + { + + } + + @Override + public void onActivityResumed (Activity activity) + { + if (host == 0) + return; + + // Ensure that navigation/status bar visibility is correctly restored. + handleAppResumed (host); + } +} diff --git a/modules/juce_core/native/java/AndroidSharingContentProvider.java b/modules/juce_gui_basics/native/javacore/app/com/roli/juce/JuceSharingContentProvider.java similarity index 82% rename from modules/juce_core/native/java/AndroidSharingContentProvider.java rename to modules/juce_gui_basics/native/javacore/app/com/roli/juce/JuceSharingContentProvider.java index 97f7f99409..bcf0718aa9 100644 --- a/modules/juce_core/native/java/AndroidSharingContentProvider.java +++ b/modules/juce_gui_basics/native/javacore/app/com/roli/juce/JuceSharingContentProvider.java @@ -1,4 +1,4 @@ -package com.juce; +package com.roli.juce; import android.content.ContentProvider; import android.content.ContentValues; @@ -9,19 +9,15 @@ import android.database.MatrixCursor; import android.net.Uri; import android.os.FileObserver; import android.os.ParcelFileDescriptor; + import java.lang.String; -public final class SharingContentProvider extends ContentProvider +public final class JuceSharingContentProvider extends ContentProvider { - private Object lock = new Object(); - - private native void contentSharerFileObserverEvent (long host, int event, String path); + private Object lock = new Object (); private native Cursor contentSharerQuery (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder); - - private native void contentSharerCursorClosed (long host); - private native AssetFileDescriptor contentSharerOpenFile (Uri uri, String mode); private native String[] contentSharerGetStreamTypes (Uri uri, String mimeTypeFilter); @@ -40,6 +36,8 @@ public final class SharingContentProvider extends ContentProvider } private long host; + + private native void contentSharerFileObserverEvent (long host, int event, String path); } public final class ProviderCursor extends MatrixCursor @@ -52,18 +50,20 @@ public final class SharingContentProvider extends ContentProvider } @Override - public void close() + public void close () { - super.close(); + super.close (); contentSharerCursorClosed (host); } + private native void contentSharerCursorClosed (long host); + private long host; } @Override - public boolean onCreate() + public boolean onCreate () { return true; } @@ -120,13 +120,12 @@ public final class SharingContentProvider extends ContentProvider AssetFileDescriptor result = contentSharerOpenFile (uri, mode); if (result != null) - return result.getParcelFileDescriptor(); + return result.getParcelFileDescriptor (); return null; } } -$$ContentProviderApi11 - @Override + public String[] getStreamTypes (Uri uri, String mimeTypeFilter) { synchronized (lock) @@ -134,5 +133,4 @@ $$ContentProviderApi11 return contentSharerGetStreamTypes (uri, mimeTypeFilter); } } -ContentProviderApi11$$ } diff --git a/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp b/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp index 73dbfcbdee..c8b58c7e1e 100644 --- a/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp +++ b/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp @@ -23,23 +23,76 @@ ============================================================================== */ - namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - FIELD (providers, "providers", "[Landroid/content/pm/ProviderInfo;") - -DECLARE_JNI_CLASS (AndroidPackageInfo, "android/content/pm/PackageInfo") -#undef JNI_CLASS_MEMBERS +//============================================================================== +// This byte-code is generated from native/javacore/app/com/roli/juce/JuceSharingContentProvider.java with min sdk version 16 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const uint8 javaJuceSharingContentProvider[] = +{31,139,8,8,143,106,229,91,0,3,74,117,99,101,83,104,97,114,105,110,103,67,111,110,116,101,110,116,80,114,111,118,105,100, +101,114,46,100,101,120,0,149,151,93,108,20,85,20,199,207,157,153,157,253,236,178,91,170,20,145,178,229,83,80,216,242,165, +96,5,11,45,72,183,91,139,161,52,218,190,56,221,157,148,129,221,153,101,102,118,133,23,2,106,162,209,196,24,125,64,19,73, +48,33,106,140,15,36,26,227,131,49,152,24,163,241,65,77,148,248,160,209,152,152,24,193,68,227,131,6,37,241,127,63,118,219, +173,197,232,194,111,238,185,231,156,123,238,185,231,222,153,206,148,237,19,137,190,173,219,105,239,208,216,231,67,47,106, +177,200,154,39,135,207,172,189,226,63,113,230,173,189,99,175,63,244,123,185,131,168,70,68,39,38,182,117,146,250,157,79, +17,141,146,212,223,4,46,48,34,110,252,3,109,4,237,103,26,209,82,222,71,171,163,189,132,203,80,156,40,103,16,125,111,18, +253,4,126,6,191,129,107,224,58,232,137,18,245,130,53,96,3,216,2,14,131,6,120,25,188,11,190,1,191,128,100,140,104,19,112, +192,235,224,50,184,6,110,193,28,187,192,3,192,6,117,240,52,120,6,60,15,206,130,115,224,101,240,10,120,3,188,9,222,6,159, +128,175,192,183,224,42,136,38,136,214,129,33,48,5,60,240,8,56,5,206,130,87,193,69,240,54,120,31,124,12,62,5,95,130,31, +192,21,240,43,248,19,24,73,162,197,96,57,88,5,242,224,78,176,27,12,131,7,65,9,56,224,56,56,9,78,129,199,192,83,0,101,37, +148,142,16,138,208,37,148,159,176,45,148,6,139,64,6,100,73,238,193,98,208,165,246,229,102,176,4,116,147,220,143,91,193, +106,176,134,228,190,240,223,195,168,189,166,228,10,228,152,154,235,4,100,148,65,236,231,105,165,71,233,233,89,200,248,47, +108,252,23,83,50,247,143,170,60,94,48,229,92,205,3,179,92,201,231,249,62,43,249,53,200,43,148,124,17,242,42,37,191,11, +121,165,146,63,130,220,171,228,47,32,231,148,252,181,41,215,177,120,78,14,93,42,135,4,170,181,85,212,42,69,247,137,122, +201,126,82,245,83,168,214,157,196,215,28,19,99,13,172,176,143,248,154,22,137,190,9,253,58,17,51,45,250,9,81,105,222,74, +125,2,255,214,171,120,36,218,36,109,16,109,156,238,17,241,101,220,20,42,113,187,104,53,186,67,180,58,109,20,45,163,77, +202,190,89,180,81,218,34,90,131,182,171,252,250,213,184,93,162,53,105,183,26,191,71,237,253,1,177,231,49,149,151,172,185, +169,106,193,247,171,15,157,109,50,61,113,94,178,170,70,77,251,0,236,35,202,158,82,118,109,142,253,32,236,211,202,206,245, +157,144,187,83,179,114,111,74,158,201,13,41,238,31,17,250,231,146,114,142,41,198,168,150,211,104,128,38,53,126,66,117, +120,242,179,118,46,41,207,137,151,209,225,127,8,91,89,235,139,146,198,210,34,119,83,248,92,104,197,208,97,53,104,32,50, +169,105,136,17,129,149,231,117,49,41,215,121,8,241,107,227,113,210,54,167,17,139,137,92,222,73,202,181,214,50,60,183,149, +168,79,45,195,207,253,84,198,16,59,25,17,167,154,232,189,164,90,7,246,155,199,229,249,125,152,148,117,24,239,53,104,57, +171,245,165,104,139,145,162,30,150,197,222,247,176,117,34,183,152,152,39,78,186,170,212,103,173,56,89,68,150,119,211,229, +57,58,77,100,133,103,86,83,151,153,157,239,251,121,243,117,252,203,124,166,26,115,37,41,239,233,241,45,24,163,241,49,131, +145,20,237,128,159,155,225,51,165,88,143,150,101,157,184,222,118,189,3,215,117,76,222,227,89,17,167,19,126,188,202,140, +174,183,205,221,48,121,5,111,60,119,68,172,33,158,154,173,89,206,160,182,223,29,243,250,59,230,245,121,55,138,168,89,220, +161,186,144,179,226,94,213,148,28,17,109,151,208,102,91,122,93,84,47,218,58,151,89,209,231,232,170,205,170,216,252,126, +202,42,61,151,155,177,179,202,175,139,204,123,28,215,9,119,19,27,38,99,184,88,44,82,132,95,139,196,10,180,162,80,47,217, +135,142,88,190,227,206,12,122,110,104,187,225,65,223,107,56,101,219,223,116,212,106,88,196,138,164,193,85,231,254,102,81, +252,168,183,104,185,101,223,115,202,249,146,28,146,159,55,180,159,86,220,200,101,194,170,212,237,160,159,214,255,195,193, +183,131,252,158,32,176,195,253,78,197,30,178,131,146,239,212,66,15,177,150,182,92,203,86,104,77,91,129,157,31,172,251, +129,215,54,77,203,52,106,133,190,115,162,233,144,109,57,184,118,152,63,236,59,115,195,121,65,158,207,53,54,29,216,126, +131,103,221,59,215,116,208,242,75,118,101,126,50,59,139,37,175,154,247,189,138,147,63,138,210,229,111,92,191,213,77,161, +153,203,189,255,127,104,123,122,27,254,115,128,126,90,89,44,91,149,134,115,44,111,185,174,23,90,161,227,185,249,125,110, +169,226,5,220,187,98,5,216,131,158,5,124,134,93,23,25,75,123,239,2,246,81,187,58,173,28,248,54,118,22,249,41,201,87,44, +119,38,63,54,125,212,46,133,237,186,67,33,207,174,159,210,237,197,160,174,133,86,72,108,130,244,137,97,156,184,137,2,25, +19,5,33,225,236,77,20,113,112,39,138,5,28,92,126,29,38,54,73,139,167,22,152,37,105,149,74,118,16,236,175,88,51,1,69,248, +98,109,74,150,188,74,189,234,222,111,85,237,128,150,170,195,198,171,214,204,101,144,187,149,169,167,205,52,55,173,125,13, +168,105,89,155,253,62,59,196,164,182,85,29,63,89,67,220,155,218,140,99,53,219,229,1,168,179,77,253,64,221,246,79,146,89, +182,43,118,104,83,196,22,97,151,204,216,225,66,39,141,210,51,237,83,68,209,231,18,25,71,188,32,164,56,191,142,123,135, +177,66,211,113,145,104,72,70,197,43,29,35,163,106,5,199,40,93,117,170,54,119,71,212,16,149,53,170,94,25,67,93,84,129,98, +158,59,136,184,200,33,234,185,114,113,29,30,82,110,221,124,240,104,174,192,168,89,225,17,74,212,124,143,239,45,14,0,69, +142,203,101,224,118,173,87,144,71,128,229,72,75,71,75,220,227,163,254,113,212,54,28,243,203,124,246,240,136,19,144,201, +175,171,251,200,172,215,202,124,118,189,238,59,252,82,161,72,131,63,21,200,20,77,64,155,244,3,219,215,71,211,27,119,109, +164,187,40,154,222,53,73,203,140,3,219,7,118,72,213,42,173,111,32,154,158,196,147,24,38,178,244,194,208,190,104,154,30, +99,90,97,39,20,14,205,176,2,250,227,90,97,20,205,16,156,168,170,21,238,22,166,134,20,138,58,254,116,108,156,26,193,147, +119,36,50,178,103,104,223,126,97,157,50,10,163,34,150,214,193,70,186,83,90,90,91,107,100,239,94,114,75,83,88,166,45,98, +35,183,106,221,137,238,36,105,26,195,159,238,103,115,145,211,167,141,75,49,237,81,141,76,246,93,140,171,53,174,142,157, +57,109,60,30,103,80,39,216,133,56,49,35,110,104,73,232,46,9,93,147,69,236,199,56,99,127,129,139,9,198,62,0,95,129,171, +224,124,146,177,31,193,75,41,249,110,75,234,89,222,108,155,223,30,252,57,223,252,254,208,105,246,27,196,160,217,239,16, +222,54,191,69,76,154,253,30,209,51,82,230,127,207,88,78,190,75,15,64,54,115,82,207,223,161,88,70,190,103,139,119,228,156, +156,151,127,191,232,202,159,191,243,24,57,57,31,127,47,34,53,86,188,123,101,100,174,252,91,233,111,138,244,241,33,100,13, +0,0}; -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ FIELD (authority, "authority", "Ljava/lang/String;") DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Landroid/os/ParcelFileDescriptor;JJ)V") \ METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \ METHOD (getLength, "getLength", "()J") @@ -47,28 +100,13 @@ DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo") DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (close, "close", "()V") DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH ";JLjava/lang/String;I)V") \ - METHOD (startWatching, "startWatching", "()V") \ - METHOD (stopWatching, "stopWatching", "()V") - -DECLARE_JNI_CLASS (JuceContentProviderFileObserver, JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH "$ProviderFileObserver") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \ - METHOD (constructor, "", "(L" JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH ";J[Ljava/lang/String;)V") - -DECLARE_JNI_CLASS (JuceContentProviderFileObserverCursor, JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH "$ProviderCursor") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;") DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor") @@ -90,8 +128,8 @@ public: const LocalRef& contentProvider, const LocalRef& resultColumns) : owner (ownerToUse), - cursor (GlobalRef (LocalRef (env->NewObject (JuceContentProviderFileObserverCursor, - JuceContentProviderFileObserverCursor.constructor, + cursor (GlobalRef (LocalRef (env->NewObject (JuceContentProviderCursor, + JuceContentProviderCursor.constructor, contentProvider.get(), reinterpret_cast (this), resultColumns.get())))) @@ -107,11 +145,35 @@ public: MessageManager::callAsync ([this] { owner.cursorClosed (*this); }); } + void addRow (LocalRef& values) + { + auto* env = getEnv(); + + env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get()); + } + private: Owner& owner; GlobalRef cursor; + + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \ + METHOD (constructor, "", "(Lcom/roli/juce/JuceSharingContentProvider;J[Ljava/lang/String;)V") \ + CALLBACK (contentSharerCursorClosed, "contentSharerCursorClosed", "(J)V") \ + + DECLARE_JNI_CLASS (JuceContentProviderCursor, "com/roli/juce/JuceSharingContentProvider$ProviderCursor") + #undef JNI_CLASS_MEMBERS + + static void JNICALL contentSharerCursorClosed(JNIEnv*, jobject, jlong host) + { + if (auto* myself = reinterpret_cast (host)) + myself->cursorClosed(); + } }; +AndroidContentSharerCursor::JuceContentProviderCursor_Class AndroidContentSharerCursor::JuceContentProviderCursor; + //============================================================================== class AndroidContentSharerFileObserver { @@ -182,8 +244,26 @@ private: Owner& owner; String filepath; GlobalRef fileObserver; + + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(Lcom/roli/juce/JuceSharingContentProvider;JLjava/lang/String;I)V") \ + METHOD (startWatching, "startWatching", "()V") \ + METHOD (stopWatching, "stopWatching", "()V") \ + CALLBACK (contentSharerFileObserverEvent, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \ + + DECLARE_JNI_CLASS (JuceContentProviderFileObserver, "com/roli/juce/JuceSharingContentProvider$ProviderFileObserver") + #undef JNI_CLASS_MEMBERS + + static void JNICALL contentSharerFileObserverEvent (JNIEnv*, jobject /*fileObserver*/, jlong host, int event, jstring path) + { + if (auto* myself = reinterpret_cast (host)) + myself->onFileEvent (event, LocalRef (path)); + } }; +AndroidContentSharerFileObserver::JuceContentProviderFileObserver_Class AndroidContentSharerFileObserver::JuceContentProviderFileObserver; + //============================================================================== class AndroidContentSharerPrepareFilesThread : private Thread { @@ -220,7 +300,7 @@ public: private: struct StreamCloser { - StreamCloser (jobject streamToUse) + StreamCloser (const LocalRef& streamToUse) : stream (GlobalRef (streamToUse)) { } @@ -289,7 +369,7 @@ private: URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename) { - auto resources = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getResources)); + auto resources = LocalRef (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources)); int fileId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (filename).get(), javaString ("raw").get(), javaString (packageName).get()); @@ -386,8 +466,7 @@ class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl, public: ContentSharerNativeImpl (ContentSharer& cs) : owner (cs), - packageName (juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (android.activity, - JuceAppActivity.getPackageName)))), + packageName (juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)))), uriBase ("content://" + packageName + ".sharingcontentprovider/") { } @@ -431,7 +510,14 @@ public: auto chooserIntent = LocalRef (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser, intent.get(), javaString ("Choose share target").get())); - env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003); + WeakReference weakRef (this); + + startAndroidActivityForResult (chooserIntent, 1003, + [weakRef] (int /*requestCode*/, int resultCode, LocalRef /*intentData*/) mutable + { + if (weakRef != nullptr) + weakRef->sharingFinished (resultCode); + }); } //============================================================================== @@ -446,7 +532,7 @@ public: } //============================================================================== - void* openFile (const LocalRef& contentProvider, + jobject openFile (const LocalRef& contentProvider, const LocalRef& uri, const LocalRef& mode) { ignoreUnused (mode); @@ -466,7 +552,7 @@ public: return getAssetFileDescriptor (env, contentProvider, uriElements.filepath); } - void* query (const LocalRef& contentProvider, const LocalRef& uri, + jobject query (const LocalRef& contentProvider, const LocalRef& uri, const LocalRef& projection, const LocalRef& selection, const LocalRef& selectionArgs, const LocalRef& sortOrder) { @@ -521,13 +607,11 @@ public: } } - auto nativeCursor = cursor->getNativeCursor(); - env->CallVoidMethod (nativeCursor, JuceContentProviderFileObserverCursor.addRow, values.get()); - - return nativeCursor; + cursor->addRow (values); + return cursor->getNativeCursor(); } - void* getStreamTypes (const LocalRef& uri, const LocalRef& mimeTypeFilter) + jobjectArray getStreamTypes (const LocalRef& uri, const LocalRef& mimeTypeFilter) { auto* env = getEnv(); @@ -558,8 +642,7 @@ private: { auto* env = getEnv(); - auto packageManager = LocalRef (env->CallObjectMethod (android.activity, - JuceAppActivity.getPackageManager)); + LocalRef packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager)); constexpr int getProviders = 8; auto packageInfo = LocalRef (env->CallObjectMethod (packageManager, @@ -620,8 +703,14 @@ private: AndroidIntent.createChooser, intent.get(), javaString ("Choose share target").get())); + WeakReference weakRef (this); - env->CallVoidMethod (android.activity, JuceAppActivity.startActivityForResult, chooserIntent.get(), 1003); + startAndroidActivityForResult (chooserIntent, 1003, + [weakRef] (int /*requestCode*/, int resultCode, LocalRef /*intentData*/) mutable + { + if (weakRef != nullptr) + weakRef->sharingFinished (resultCode); + }); } void decrementPendingFileCountAndNotifyOwnerIfReady() @@ -674,7 +763,7 @@ private: return StringArray ("_display_name", "_size"); } - void* getAssetFileDescriptor (JNIEnv* env, const LocalRef& contentProvider, + jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef& contentProvider, const String& filepath) { // This function can be called from multiple threads. @@ -740,89 +829,59 @@ private: WeakReference::Master masterReference; friend class WeakReference; -}; -//============================================================================== -ContentSharer::Pimpl* ContentSharer::createPimpl() -{ - return new ContentSharerNativeImpl (*this); -} - -//============================================================================== -void* juce_contentSharerQuery (void* contentProvider, void* uri, void* projection, - void* selection, void* selectionArgs, void* sortOrder) -{ - auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get(); - return pimpl->query (LocalRef (static_cast (contentProvider)), - LocalRef (static_cast (uri)), - LocalRef (static_cast (projection)), - LocalRef (static_cast (selection)), - LocalRef (static_cast (selectionArgs)), - LocalRef (static_cast (sortOrder))); -} - -void* juce_contentSharerOpenFile (void* contentProvider, void* uri, void* mode) -{ - auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get(); - return pimpl->openFile (LocalRef (static_cast (contentProvider)), - LocalRef (static_cast (uri)), - LocalRef (static_cast (mode))); -} - -void juce_contentSharingCompleted (int resultCode) -{ - auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get(); - return pimpl->sharingFinished (resultCode); -} + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + CALLBACK (contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;") \ + CALLBACK (contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \ + CALLBACK (contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \ -void* juce_contentSharerGetStreamTypes (void* uri, void* mimeTypeFilter) -{ - auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get(); - return pimpl->getStreamTypes (LocalRef (static_cast (uri)), - LocalRef (static_cast (mimeTypeFilter))); -} -//============================================================================== -JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerFileObserverEvent, void, - (JNIEnv* env, jobject /*fileObserver*/, jlong host, int event, jstring path)) -{ - setEnv (env); + DECLARE_JNI_CLASS_WITH_BYTECODE (JuceSharingContentProvider, "com/roli/juce/JuceSharingContentProvider", 16, javaJuceSharingContentProvider, sizeof(javaJuceSharingContentProvider)) + #undef JNI_CLASS_MEMBERS - reinterpret_cast (host)->onFileEvent (event, LocalRef (path)); -} + static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection, + jobject selection, jobjectArray selectionArgs, jobject sortOrder) + { + if (auto *pimpl = (ContentSharer::ContentSharerNativeImpl *) ContentSharer::getInstance ()->pimpl.get ()) + return pimpl->query (LocalRef (static_cast (contentProvider)), + LocalRef (static_cast (uri)), + LocalRef ( + static_cast (projection)), + LocalRef (static_cast (selection)), + LocalRef ( + static_cast (selectionArgs)), + LocalRef (static_cast (sortOrder))); + + return nullptr; + } -//============================================================================== -JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerQuery, jobject, - (JNIEnv* env, jobject contentProvider, jobject uri, jobjectArray projection, - jobject selection, jobjectArray selectionArgs, jobject sortOrder)) -{ - setEnv (env); + static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode) + { + if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get()) + return pimpl->openFile (LocalRef (static_cast (contentProvider)), + LocalRef (static_cast (uri)), + LocalRef (static_cast (mode))); - return (jobject) juce_contentSharerQuery (contentProvider, uri, projection, selection, selectionArgs, sortOrder); -} + return nullptr; + } -JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerCursorClosed, void, - (JNIEnv* env, jobject /*cursor*/, jlong host)) -{ - setEnv (env); + static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter) + { + if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get()) + return pimpl->getStreamTypes (LocalRef (static_cast (uri)), + LocalRef (static_cast (mimeTypeFilter))); - reinterpret_cast (host)->cursorClosed(); -} + return nullptr; + } +}; -JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerOpenFile, jobject, - (JNIEnv* env, jobject contentProvider, jobject uri, jstring mode)) +//============================================================================== +ContentSharer::Pimpl* ContentSharer::createPimpl() { - setEnv (env); - - return (jobject) juce_contentSharerOpenFile ((void*) contentProvider, (void*) uri, (void*) mode); + return new ContentSharerNativeImpl (*this); } -JUCE_JNI_CALLBACK (JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME, contentSharerGetStreamTypes, jobject, - (JNIEnv* env, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter)) -{ - setEnv (env); - - return (jobject) juce_contentSharerGetStreamTypes ((void*) uri, (void*) mimeTypeFilter); -} +ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider_Class ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider; } // namespace juce diff --git a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp index 72a5d05c4e..d95d4671b4 100644 --- a/modules/juce_gui_basics/native/juce_android_FileChooser.cpp +++ b/modules/juce_gui_basics/native/juce_android_FileChooser.cpp @@ -30,6 +30,7 @@ namespace juce class FileChooser::Native : public FileChooser::Pimpl { public: + //============================================================================== Native (FileChooser& fileChooser, int flags) : owner (fileChooser) { if (currentFileChooser == nullptr) @@ -37,7 +38,7 @@ public: currentFileChooser = this; auto* env = getEnv(); - auto sdkVersion = env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion); + auto sdkVersion = getAndroidSDKVersion(); auto saveMode = ((flags & FileBrowserComponent::saveMode) != 0); auto selectsDirectories = ((flags & FileBrowserComponent::canSelectDirectories) != 0); @@ -64,8 +65,8 @@ public: : "android.intent.action.GET_CONTENT"))); - intent = GlobalRef (env->NewObject (AndroidIntent, AndroidIntent.constructWithString, - javaString (action).get())); + intent = GlobalRef (LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructWithString, + javaString (action).get()))); if (owner.startingFile != File()) { @@ -135,6 +136,7 @@ public: ~Native() { + masterReference.clear(); currentFileChooser = nullptr; } @@ -146,13 +148,26 @@ public: void launch() override { + auto* env = getEnv(); + if (currentFileChooser != nullptr) - android.activity.callVoidMethod (JuceAppActivity.startActivityForResult, intent.get(), /*READ_REQUEST_CODE*/ 42); + { + WeakReference myself (this); + + startAndroidActivityForResult (LocalRef (env->NewLocalRef (intent.get())), /*READ_REQUEST_CODE*/ 42, + [myself] (int requestCode, int resultCode, LocalRef intentData) mutable + { + if (myself != nullptr) + myself->onActivityResult (requestCode, resultCode, intentData); + }); + } else + { jassertfalse; // There is already a file chooser running + } } - void completed (int resultCode, jobject intentData) + void onActivityResult (int /*requestCode*/, int resultCode, const LocalRef& intentData) { currentFileChooser = nullptr; auto* env = getEnv(); @@ -161,7 +176,7 @@ public: if (resultCode == /*Activity.RESULT_OK*/ -1 && intentData != nullptr) { - LocalRef uri (env->CallObjectMethod (intentData, AndroidIntent.getData)); + LocalRef uri (env->CallObjectMethod (intentData.get(), AndroidIntent.getData)); if (uri != nullptr) { @@ -197,23 +212,23 @@ public: } private: + JUCE_DECLARE_WEAK_REFERENCEABLE (Native) + FileChooser& owner; GlobalRef intent; }; FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr; -void juce_fileChooserCompleted (int resultCode, void* intentData) -{ - if (FileChooser::Native::currentFileChooser != nullptr) - FileChooser::Native::currentFileChooser->completed (resultCode, (jobject) intentData); -} - - FileChooser::Pimpl* FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*) { - return new FileChooser::Native (owner, flags); + if (FileChooser::Native::currentFileChooser == nullptr) + return new FileChooser::Native (owner, flags); + + // there can only be one file chooser on Android at a once + jassertfalse; + return nullptr; } bool FileChooser::isPlatformDialogAvailable() diff --git a/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/modules/juce_gui_basics/native/juce_android_Windowing.cpp index c8dd673bfa..1918caf660 100644 --- a/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -24,10 +24,175 @@ ============================================================================== */ -extern juce::JUCEApplicationBase* juce_CreateApplication(); // (from START_JUCE_APPLICATION) - namespace juce { +// This byte-code is generated from native/java/com/roli/juce/ComponentPeerView.java with min sdk version 16 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const uint8 javaComponentPeerView[] = +{31,139,8,8,248,105,229,91,0,3,67,111,109,112,111,110,101,110,116,80,101,101,114,86,105,101,119,46,100,101,120,0,165,154, +11,124,92,85,157,199,255,231,220,59,51,201,100,50,153,76,210,164,73,38,233,228,217,105,155,38,83,160,208,146,180,244,13, +105,147,182,52,105,160,73,129,222,76,110,146,105,39,247,78,103,38,143,10,46,225,177,148,69,228,225,118,1,5,93,68,100,171, +162,219,34,178,5,92,101,149,197,174,162,91,124,176,85,235,10,10,31,145,45,136,221,186,174,136,178,191,115,238,153,100, +250,2,117,39,159,239,252,255,231,127,254,247,188,207,255,156,153,204,160,57,233,141,158,191,152,158,156,245,224,205,149, +149,85,69,59,14,196,31,217,241,202,99,15,124,242,195,239,188,245,102,211,239,146,201,249,68,73,34,154,236,189,32,72,234, +245,232,60,162,227,228,216,23,129,176,78,116,17,228,43,144,165,194,215,67,52,4,57,59,143,72,131,188,172,128,232,112,13, +209,126,200,19,229,68,39,193,59,160,116,54,81,57,168,2,53,160,14,52,130,121,96,21,88,11,58,64,23,216,2,118,130,7,193,211, +224,69,240,107,80,92,129,54,128,237,96,28,220,9,30,6,79,129,239,129,95,129,202,74,162,86,208,1,110,0,15,129,239,130,223, +129,198,42,162,30,240,0,248,15,240,54,168,9,17,45,7,6,184,22,220,1,30,3,71,193,27,32,80,77,212,0,86,131,43,192,45,224,33, +176,31,60,1,158,4,255,12,158,5,135,193,17,240,34,56,6,94,6,175,130,215,193,175,192,73,240,54,248,35,112,99,140,242,129, +31,148,130,10,80,13,234,64,19,152,15,150,131,110,112,13,216,5,38,192,77,224,78,240,0,120,24,28,4,79,131,23,193,15,193, +113,112,18,188,3,244,57,68,133,160,20,212,130,22,176,28,116,129,94,112,21,136,129,33,48,10,174,5,183,128,143,131,71,193, +19,224,107,224,8,248,49,120,11,252,30,248,194,24,103,80,15,154,193,82,176,1,244,131,36,184,30,220,9,62,15,190,8,158,4,95, +6,223,6,63,5,191,6,84,139,182,129,122,112,1,88,11,122,128,1,118,129,155,193,29,96,31,184,23,124,10,236,7,79,130,195,224, +135,224,21,112,2,188,13,220,117,152,43,80,5,26,192,50,208,9,186,193,0,24,1,73,48,9,174,5,55,128,219,192,93,224,30,240,25, +240,21,112,4,252,28,80,61,81,9,104,2,139,193,70,208,11,76,48,6,166,192,157,224,62,240,16,56,8,190,10,190,1,142,128,31, +129,151,193,27,224,36,248,45,168,106,192,88,129,21,96,45,216,10,98,224,58,112,59,248,24,120,28,124,11,188,6,126,3,244,70, +244,11,132,65,11,88,6,186,192,118,48,14,174,3,55,131,219,193,71,192,189,224,227,224,31,192,103,193,65,240,20,120,6,188,0, +142,129,55,193,219,224,15,128,154,80,7,200,107,114,246,175,11,96,75,147,31,20,129,0,40,6,34,32,148,144,179,231,103,129, +50,80,46,246,61,192,182,35,108,47,194,118,34,108,25,194,242,38,44,63,154,35,226,133,51,213,164,134,148,208,37,82,213,210, +92,16,1,8,49,132,208,67,11,64,51,88,8,90,64,43,136,170,184,115,30,56,31,92,0,22,131,37,224,82,208,5,54,130,77,96,51,57, +125,200,190,116,37,31,44,115,250,197,84,218,173,116,97,23,125,229,202,158,167,244,253,176,123,149,159,120,249,84,222,1, +101,247,43,187,87,141,75,86,159,149,227,47,198,229,80,153,163,139,49,121,70,61,91,171,124,10,212,88,136,54,20,170,241,56, +172,252,133,126,68,249,71,148,127,161,26,163,163,101,78,223,23,40,251,82,165,139,114,46,86,250,75,208,219,148,126,28,122, +187,210,79,66,95,166,116,29,147,183,92,233,1,232,151,40,61,4,125,133,210,27,160,175,202,206,75,142,253,130,28,189,29,250, +26,165,175,201,177,111,206,209,175,204,209,119,228,148,57,146,99,79,66,95,173,244,201,28,251,93,101,51,186,24,195,149,74, +159,202,41,231,214,28,127,49,110,107,179,207,194,190,78,233,247,229,248,236,207,209,15,148,59,235,168,89,141,231,101,74, +63,4,123,135,210,159,129,190,94,233,207,67,239,84,250,247,161,111,80,250,177,28,251,43,57,246,227,229,98,78,25,93,69,142, +60,70,98,223,132,233,74,37,239,148,146,209,71,148,220,167,228,223,41,121,143,146,247,42,255,7,73,236,181,102,250,123,41, +171,232,71,36,246,93,136,126,168,228,207,164,172,164,87,197,92,98,53,223,37,101,21,125,66,202,102,250,178,148,5,244,85, +185,254,106,233,66,18,123,166,132,14,146,216,35,213,244,117,18,235,222,77,163,82,6,232,3,144,249,200,103,82,46,164,231, +72,172,203,90,153,46,80,246,2,236,162,111,200,254,57,233,34,212,155,144,178,152,44,149,190,86,172,51,101,15,96,103,238, +86,233,49,41,53,26,39,39,214,76,40,57,41,37,163,41,37,111,32,177,215,56,165,164,172,163,235,73,196,163,26,89,95,9,162, +195,19,82,46,162,127,146,114,22,61,69,98,63,54,81,159,146,207,146,136,89,141,210,191,2,145,228,14,41,235,233,155,82,46, +166,239,203,241,156,43,243,43,81,194,151,228,56,206,147,233,42,216,47,87,114,139,148,62,234,150,178,132,126,160,228,139, +114,92,231,72,255,16,70,178,71,202,8,109,149,178,132,182,43,105,72,25,37,83,202,74,218,37,165,91,222,175,66,170,127,33, +68,191,180,148,249,180,71,74,47,93,167,242,63,40,101,30,253,149,148,206,56,132,208,179,155,148,188,89,201,191,150,114,54, +221,162,228,94,101,191,85,202,114,250,27,37,111,83,242,67,82,86,208,237,82,182,210,253,74,62,32,101,136,62,171,214,209, +231,148,124,84,201,207,171,252,47,168,244,63,42,121,64,173,179,199,164,12,211,23,165,108,164,199,165,116,230,43,164,230, +75,164,15,169,245,249,164,148,206,252,133,16,237,159,150,114,1,29,86,242,223,164,108,160,231,165,44,165,239,72,57,159, +254,93,165,143,40,191,23,148,252,174,202,255,158,234,255,207,73,196,224,32,101,164,188,144,62,38,247,129,159,94,38,17, +119,157,245,219,132,104,107,147,136,189,26,13,72,201,233,83,36,226,111,17,125,148,156,179,142,200,137,211,34,62,137,51, +99,7,228,87,84,176,174,82,107,184,73,249,137,252,36,242,95,80,249,226,60,40,38,231,60,189,79,61,63,5,249,187,200,140,255, +109,208,239,142,56,231,217,253,144,15,129,71,35,206,185,245,37,105,231,206,222,107,18,251,18,119,175,96,1,37,3,6,108,65, +218,30,240,202,51,42,15,41,241,124,41,124,250,69,125,238,82,170,227,62,248,229,227,233,158,116,1,245,156,239,35,43,112, +62,188,124,44,72,75,89,251,140,53,124,17,118,199,140,207,34,248,120,181,45,56,178,182,52,50,58,216,115,55,105,250,226,29, +149,180,53,237,165,37,90,25,5,181,237,208,178,254,61,153,2,218,29,104,194,62,247,233,187,3,243,33,11,244,221,209,5,180, +218,37,252,151,106,30,186,104,135,139,130,37,226,25,63,234,42,195,92,88,129,89,104,239,233,117,68,190,35,206,113,77,142, +201,210,38,231,78,210,31,244,169,190,214,97,46,146,225,90,140,118,127,192,79,253,37,133,178,223,76,254,33,126,55,57,119, +128,100,64,220,52,252,211,246,203,167,237,17,105,231,234,102,176,77,221,129,122,2,133,114,126,52,216,69,189,70,147,115, +46,247,132,11,209,74,113,3,65,15,107,69,187,171,80,179,127,218,111,228,156,126,33,233,231,71,92,16,61,177,225,39,214,127, +208,29,156,149,12,151,211,103,168,78,23,179,231,65,171,250,247,33,186,233,85,148,140,214,211,190,252,254,125,1,10,233,46, +148,112,13,61,76,94,125,137,126,21,205,241,138,52,195,202,237,123,160,8,90,63,197,105,251,253,136,180,225,30,218,134,247, +173,242,189,23,43,181,14,43,35,25,16,61,45,209,75,169,126,229,66,106,98,34,142,89,97,23,218,211,35,106,114,121,49,126, +110,18,37,230,33,102,5,53,43,48,128,167,124,122,165,235,91,20,108,170,31,106,165,160,103,32,175,132,186,242,220,158,96, +121,40,175,64,106,86,244,106,234,113,251,180,37,90,41,5,121,176,161,126,77,148,130,174,221,129,29,232,157,207,221,229, +214,93,193,89,65,41,173,168,73,127,171,99,12,2,28,81,204,167,207,209,157,214,239,163,200,39,11,245,200,73,112,2,188,5, +126,4,142,98,202,229,29,115,230,53,117,9,253,89,233,211,95,78,190,56,191,203,16,143,76,114,206,19,174,213,223,204,234, +110,100,243,166,24,159,123,43,107,188,133,57,243,45,94,63,80,235,160,59,92,36,247,177,88,127,34,125,44,187,110,162,203, +168,86,19,179,233,236,175,87,213,186,236,94,81,68,101,92,222,87,249,82,228,248,165,37,25,189,152,116,22,121,123,166,252, +55,154,156,251,168,184,67,247,161,14,191,170,67,188,254,187,201,217,255,193,64,209,244,186,252,131,106,207,214,64,177, +172,135,171,117,204,231,58,119,68,43,176,88,236,28,220,5,117,249,92,144,34,127,212,84,121,121,115,157,152,225,151,207,57, +55,221,64,142,205,13,155,240,44,159,251,222,125,88,162,250,144,245,15,191,143,255,210,233,62,59,237,152,119,150,118,44, +58,139,109,73,142,77,87,117,93,50,215,185,55,7,153,136,188,253,81,78,142,212,104,219,34,68,145,183,182,69,61,180,45,234, +86,214,124,172,31,220,220,3,219,162,58,242,243,176,11,202,208,158,106,10,179,98,213,95,49,246,27,231,58,49,248,236,237, +239,89,21,164,228,150,149,164,95,30,249,95,241,25,65,147,243,222,123,206,103,206,95,251,238,187,178,223,43,90,73,55,196, +51,249,176,139,122,98,115,157,251,122,176,184,123,0,79,233,226,41,174,203,153,194,248,53,123,196,103,36,145,99,5,124,136, +215,216,141,155,219,168,41,205,2,145,19,118,116,54,141,231,121,41,242,134,21,136,162,221,62,220,46,52,248,137,81,66,190, +184,232,113,167,93,217,207,96,37,106,220,4,94,228,136,250,175,159,235,124,158,10,178,233,250,57,234,199,147,69,108,169, +39,31,229,121,209,86,47,15,214,159,119,158,135,236,21,21,52,126,159,151,69,78,88,129,2,17,7,248,18,254,198,187,89,221,10, +199,208,106,175,108,175,108,167,22,44,142,252,196,25,31,81,215,71,231,58,159,129,78,29,31,167,167,194,38,250,232,70,93, +201,45,237,24,217,96,32,242,27,231,174,41,94,143,228,204,123,62,74,19,229,61,174,198,219,190,178,146,122,198,115,75,93, +130,58,157,117,118,21,214,153,243,89,149,203,207,104,255,138,103,66,40,168,251,5,120,99,241,44,98,76,91,202,208,6,86,196, +172,128,56,31,188,249,86,64,196,226,2,207,15,238,126,151,22,176,255,145,163,19,249,117,247,119,177,115,152,21,14,224,86, +233,213,45,236,53,248,186,172,64,80,72,183,231,133,241,188,229,226,116,45,142,28,123,127,207,21,142,231,55,222,223,243, +18,120,90,1,17,3,188,5,193,226,139,170,230,83,176,182,62,212,130,24,121,30,125,155,130,149,139,159,170,37,81,138,40,227, +11,162,140,112,137,144,44,232,90,227,242,185,110,220,250,220,44,43,92,234,88,202,214,184,125,238,27,135,158,43,203,150, +123,212,139,153,124,9,237,184,169,148,209,187,184,55,220,253,178,151,229,45,245,206,166,63,181,7,243,104,232,221,220,246, +252,37,45,17,37,69,126,252,231,183,96,185,106,65,227,255,187,5,203,101,11,196,182,241,200,181,38,214,150,136,21,173,74, +138,24,35,238,165,105,185,246,184,60,163,47,142,56,223,65,160,141,56,17,61,88,253,149,26,230,163,180,126,13,78,68,215, +128,27,39,162,60,221,174,166,243,116,31,91,138,146,253,184,53,70,254,8,222,9,6,235,235,112,30,106,226,60,204,195,153,215, +165,115,77,156,131,187,121,228,205,66,30,57,14,94,7,175,137,245,94,140,182,137,251,162,216,25,156,207,45,107,156,173,213, +71,234,154,231,205,207,137,137,131,145,153,189,161,41,107,34,226,220,251,150,112,15,246,151,21,222,6,15,63,5,87,68,126, +47,250,231,196,205,177,136,243,157,137,136,31,226,134,87,201,239,131,167,134,88,42,98,73,5,226,210,108,228,13,98,135,122, +217,50,86,130,209,182,162,97,138,162,199,23,74,127,43,58,135,2,122,207,162,0,238,114,13,178,252,108,78,144,91,209,26,10, +112,39,79,124,170,138,156,112,190,243,205,125,61,126,90,250,217,211,210,162,79,165,228,156,119,197,104,5,83,54,231,51, +148,35,243,148,44,87,178,74,249,55,225,51,148,72,71,84,58,130,217,212,41,123,247,83,231,163,154,235,236,57,202,213,92, +103,207,79,71,247,200,239,98,184,164,57,199,46,100,161,76,235,170,110,183,202,115,195,143,43,155,71,201,124,37,125,234, +89,63,90,228,204,175,58,207,213,51,33,53,127,226,51,147,200,111,86,109,108,206,105,183,96,161,146,45,234,121,166,238,26, +66,22,77,219,138,84,93,206,115,129,233,186,152,250,28,131,182,182,199,173,120,102,57,149,173,182,71,147,182,101,90,153, +205,166,153,234,141,155,19,45,59,141,113,220,161,215,17,95,215,65,172,131,120,7,196,122,226,235,59,169,124,131,185,103, +192,54,82,131,107,226,233,209,120,58,221,25,79,103,76,203,76,17,235,36,222,9,215,206,78,210,58,241,86,214,105,88,131,41, +59,62,216,106,36,147,173,43,99,153,248,120,60,179,167,141,46,56,213,158,76,38,226,49,35,19,183,173,134,172,79,103,124, +200,140,237,137,37,204,213,70,34,49,96,196,118,165,219,168,226,92,79,229,102,197,108,11,109,201,180,174,22,114,50,147, +155,53,156,50,146,35,241,88,186,117,181,97,141,27,40,176,230,44,89,118,194,78,173,139,39,50,102,234,220,249,93,70,38,21, +159,108,163,121,239,153,127,74,81,179,207,116,221,108,196,45,180,175,252,204,156,45,102,12,25,37,211,25,118,186,117,213, +152,53,152,48,219,168,52,215,216,177,42,110,13,138,210,103,202,24,199,204,181,98,122,214,142,155,162,240,202,83,51,186, +108,49,92,42,111,222,169,121,98,206,27,54,89,235,236,216,88,122,245,136,97,13,155,217,105,205,109,202,180,107,110,151, +166,141,151,166,236,177,100,27,93,120,102,78,79,202,52,55,13,164,205,212,184,153,66,45,151,38,236,1,35,209,105,236,177, +199,50,51,213,204,121,239,231,218,168,229,84,135,184,149,28,203,140,154,153,17,123,176,117,149,145,54,59,68,26,19,111,97, +252,228,178,104,60,183,255,218,193,120,198,78,117,88,67,118,27,205,63,183,219,25,69,46,124,31,223,46,169,119,25,150,49, +44,90,188,174,51,102,143,182,166,236,68,188,117,231,88,204,108,61,99,155,53,156,99,47,53,156,218,243,165,127,105,57,109, +84,251,126,143,182,81,93,231,160,145,24,143,239,106,53,44,203,206,200,61,213,186,214,138,37,236,116,220,26,94,157,48,210, +114,179,156,233,211,129,113,73,169,252,218,179,228,119,153,163,3,202,193,76,139,21,35,98,74,107,2,139,171,21,75,44,213, +109,238,30,51,173,24,150,117,113,110,142,83,94,93,142,169,35,145,48,135,141,196,202,88,204,76,167,215,78,198,204,164,51, +25,13,103,241,73,13,143,141,162,115,57,94,37,185,94,136,10,195,206,168,204,24,55,218,221,99,177,17,103,230,114,158,11, +230,184,108,26,216,41,55,101,117,142,173,219,140,141,165,16,171,206,241,72,55,130,128,53,44,86,204,140,45,101,14,37,80, +14,154,49,110,59,177,171,199,72,13,155,185,173,173,60,139,187,211,180,54,210,123,182,109,94,75,190,220,165,65,172,151, +120,111,7,185,122,59,240,130,186,158,220,189,235,59,214,173,91,79,58,100,135,120,23,225,184,119,125,31,50,133,210,185,94, +188,73,173,15,185,157,125,8,234,189,125,120,170,79,150,192,250,72,235,19,207,225,173,83,168,136,233,125,66,17,129,189,31, +199,65,127,7,5,251,207,156,175,146,254,179,12,151,215,144,83,214,16,141,70,167,245,69,57,250,121,57,250,249,57,250,5,208, +11,28,125,93,194,24,78,147,219,144,251,79,24,133,236,52,6,204,4,229,25,234,180,160,10,99,112,240,236,81,133,216,0,21,139, +3,100,213,88,38,99,91,155,83,40,210,28,36,247,128,141,228,40,164,12,172,228,142,201,51,129,60,49,25,250,6,201,133,179, +199,72,81,65,204,30,52,55,219,136,212,43,51,34,49,29,221,201,47,19,61,41,195,74,15,217,169,81,42,20,39,15,66,122,90,122, +163,32,231,0,66,65,246,24,210,250,96,124,104,136,152,73,46,83,132,95,242,13,205,4,218,65,202,199,26,88,233,244,112,150, +80,103,206,54,117,142,81,33,204,98,207,160,203,102,42,77,121,34,41,6,158,188,66,83,78,62,177,148,68,241,61,241,81,83,22, +122,153,25,31,30,201,80,9,212,78,181,226,54,89,221,49,44,32,75,230,59,43,139,138,160,202,118,99,183,202,230,250,102,12, +29,131,228,65,106,139,49,113,101,86,217,70,5,66,177,237,140,136,31,20,64,162,123,15,198,123,180,27,107,50,30,51,201,15, +203,86,43,46,6,79,52,90,214,127,122,56,151,93,234,141,167,227,3,241,132,152,67,241,204,21,56,208,236,137,30,123,23,154, +23,154,78,75,167,132,137,200,150,76,24,123,214,165,12,116,78,71,238,149,242,29,87,219,17,42,194,64,98,30,49,112,155,141, +49,49,193,129,105,195,22,51,141,136,48,109,89,53,189,22,168,208,177,32,114,174,177,39,176,182,166,147,91,147,84,58,157, +144,81,245,178,248,224,32,218,164,170,233,178,81,135,124,230,20,67,202,24,206,150,41,13,40,70,149,41,207,122,202,27,49, +210,242,124,165,242,145,248,160,217,109,15,101,228,153,177,46,101,143,58,61,133,11,28,123,196,92,234,35,118,58,67,44,78, +94,204,229,38,25,28,210,164,197,71,71,169,72,92,217,226,70,98,181,145,76,119,97,132,169,80,25,186,205,196,90,107,112,58, +31,201,238,140,145,202,80,190,60,165,122,246,36,77,242,73,245,26,231,196,34,119,28,113,104,151,137,42,210,29,86,58,99,32, +22,83,94,60,189,41,105,32,48,227,177,180,26,121,242,236,50,247,172,22,85,149,239,58,199,213,175,48,155,209,61,34,198,197, +149,144,59,180,16,243,101,166,68,205,27,113,228,144,158,48,135,50,228,78,152,214,112,102,132,220,170,21,204,34,221,18, +115,234,177,204,137,141,114,114,237,196,224,136,124,159,160,98,219,202,94,10,87,167,76,35,131,153,44,153,49,173,49,211, +153,148,189,71,76,239,140,81,45,129,156,39,179,107,160,106,198,212,109,140,155,217,78,99,152,50,102,174,191,28,183,83, +139,232,206,216,201,36,76,133,182,37,150,80,54,144,148,99,139,202,102,157,118,83,32,183,109,97,65,76,8,255,156,59,21,249, +237,83,194,20,229,219,86,118,253,21,74,181,107,44,145,137,39,197,152,203,36,214,80,158,136,119,210,25,30,221,241,15,152, +217,168,225,195,241,97,227,228,146,91,30,21,58,211,230,113,228,53,120,110,12,81,43,131,104,225,74,202,245,231,77,26,41, +120,202,61,235,74,202,48,197,82,84,151,50,135,197,44,166,206,125,247,166,80,202,28,181,199,77,167,229,155,172,211,66,172, +43,37,131,140,150,54,51,228,79,139,112,52,125,243,37,31,210,178,255,134,88,71,229,185,169,14,167,245,114,9,139,199,114, +238,79,242,177,206,236,218,161,217,72,157,245,106,74,179,210,217,208,179,53,158,19,75,170,206,106,22,55,31,3,97,60,237,4, +35,185,212,10,211,167,4,33,111,54,153,112,218,116,69,60,145,216,104,103,228,76,250,210,88,218,217,128,128,7,145,154,222, +194,112,22,75,198,105,23,142,125,100,99,189,204,36,103,167,157,214,116,204,212,165,122,170,103,70,226,56,223,196,123,67, +84,201,69,176,138,8,174,161,12,168,98,8,242,198,50,67,75,100,44,101,227,228,26,55,18,98,166,165,216,52,68,186,184,139,82, +145,120,207,93,31,249,194,208,99,111,77,155,20,24,63,61,250,122,199,103,122,205,38,136,77,18,159,140,130,69,196,246,208, +65,206,200,227,239,107,167,67,248,0,220,220,175,241,175,179,194,189,26,251,23,86,50,71,163,56,111,153,252,237,206,118,86, +92,28,111,231,233,234,118,122,144,115,122,149,121,252,252,226,109,252,210,137,133,244,113,206,94,71,242,12,249,8,10,243, +63,78,159,115,68,141,54,252,21,118,55,243,52,243,23,168,141,191,201,38,248,215,63,56,113,43,227,46,239,202,133,237,45, +237,237,203,251,53,26,244,94,167,49,179,165,253,145,90,77,251,52,91,192,202,103,69,107,52,254,16,227,172,184,220,197,249, +229,213,46,114,49,151,230,246,242,5,15,187,188,110,114,51,55,119,107,243,231,243,241,102,23,159,207,211,205,84,225,84,93, +193,95,103,255,37,148,151,68,199,110,11,17,206,50,255,6,122,66,227,191,100,199,133,125,39,210,244,168,38,222,143,105,188, +239,26,200,23,53,214,7,241,140,20,120,228,23,82,249,80,136,142,40,135,159,57,14,223,116,196,207,57,251,42,243,84,111,216, +176,176,111,67,95,11,25,108,135,124,234,70,141,191,196,238,64,21,31,170,90,72,191,100,252,19,108,151,167,122,47,15,86, +243,100,53,47,106,251,52,207,84,111,255,52,159,172,166,219,185,246,9,54,130,76,94,120,11,31,175,190,167,127,231,94,141, +126,202,120,31,221,200,229,83,254,189,225,208,61,244,123,238,50,62,197,126,194,158,99,87,163,210,19,92,127,141,221,196, +62,203,62,131,26,150,237,237,163,61,210,145,31,166,106,254,212,245,213,27,180,252,43,121,167,230,57,198,48,133,180,141, +175,104,102,37,69,81,135,64,167,86,240,8,227,203,218,53,223,215,88,235,50,198,52,239,71,24,95,200,66,133,151,184,188,46, +223,34,87,193,78,183,183,133,149,148,241,107,219,218,221,190,101,172,122,150,176,159,106,228,235,88,181,159,238,214,216, +47,80,127,88,99,71,49,73,193,50,238,107,230,163,213,88,42,59,35,53,46,18,178,169,222,69,47,71,231,211,135,53,182,95,12, +247,65,141,109,247,248,227,33,250,54,99,255,9,195,179,26,237,99,149,45,59,55,76,238,168,216,75,188,154,189,196,102,87, +241,26,222,171,243,71,88,249,69,142,33,36,13,65,24,242,249,28,24,66,108,118,101,86,169,34,206,152,151,179,187,194,181,83, +83,250,243,229,117,236,173,114,210,220,228,187,43,140,213,194,235,111,152,210,15,85,176,91,195,175,137,183,169,74,188, +221,87,201,248,1,112,180,146,244,162,138,34,206,228,223,60,56,78,85,33,251,193,26,188,29,170,209,111,228,148,15,216,159, +64,8,52,179,35,53,140,77,205,97,236,129,57,165,236,0,228,97,112,28,76,133,25,219,7,158,5,39,193,173,181,240,1,7,192,205, +117,140,29,2,199,5,245,140,29,109,96,250,190,70,166,239,111,100,236,88,163,206,30,158,15,191,5,156,61,11,94,91,160,190, +203,201,126,71,150,149,217,223,97,138,239,120,178,191,197,20,223,9,101,127,143,169,211,204,111,50,197,119,74,217,223,101, +102,191,183,18,191,205,212,2,142,46,190,171,99,97,231,55,72,207,67,119,135,29,187,248,63,54,11,56,223,171,201,255,109, +135,157,122,197,111,57,53,229,47,254,231,172,135,157,114,197,255,169,73,61,43,255,255,29,112,218,42,126,55,250,127,111, +26,247,20,112,42,0,0}; //============================================================================== #if JUCE_PUSH_NOTIFICATIONS && JUCE_MODULE_AVAILABLE_juce_gui_extra @@ -44,175 +209,39 @@ namespace juce extern void juce_inAppPurchaseCompleted (void*); #endif -#if ! JUCE_DISABLE_NATIVE_FILECHOOSERS - extern void juce_fileChooserCompleted (int, void*); -#endif - extern void juce_contentSharingCompleted (int); //============================================================================== -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity, - jstring appFile, jstring appDataDir)) -{ - setEnv (env); - - android.initialise (env, activity, appFile, appDataDir); - - DBG (SystemStats::getJUCEVersion()); - - JUCEApplicationBase::createInstance = &juce_CreateApplication; - - initialiseJuce_GUI(); - - if (JUCEApplicationBase* app = JUCEApplicationBase::createInstance()) - { - if (! app->initialiseApp()) - exit (app->shutdownApp()); - } - else - { - jassertfalse; // you must supply an application object for an android app! - } - - JUCE_ASSERT_MESSAGE_THREAD -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, suspendApp, void, (JNIEnv* env, jobject)) -{ - setEnv (env); - - if (auto* app = JUCEApplicationBase::getInstance()) - app->suspended(); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, resumeApp, void, (JNIEnv* env, jobject)) -{ - setEnv (env); - - if (auto* app = JUCEApplicationBase::getInstance()) - app->resumed(); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, jobject)) -{ - setEnv (env); - - JUCEApplicationBase::appWillTerminateByForce(); - - android.shutdown (env); - - jclass systemClass = (jclass) env->FindClass ("java/lang/System"); - jmethodID exitMethod = env->GetStaticMethodID (systemClass, "exit", "(I)V"); - env->CallStaticVoidMethod (systemClass, exitMethod, 0); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appActivityResult, void, (JNIEnv* env, jobject, jint requestCode, jint resultCode, jobject intentData)) -{ - setEnv (env); - - #if JUCE_IN_APP_PURCHASES && JUCE_MODULE_AVAILABLE_juce_product_unlocking - if (requestCode == 1001) - juce_inAppPurchaseCompleted (intentData); - #endif +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (create, "", "(II)V") - #if ! JUCE_DISABLE_NATIVE_FILECHOOSERS - if (requestCode == /*READ_REQUEST_CODE*/42) - juce_fileChooserCompleted (resultCode, intentData); - #endif - - if (requestCode == 1003) - juce_contentSharingCompleted (resultCode); - - ignoreUnused (intentData, requestCode); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appNewIntent, void, (JNIEnv* env, jobject, jobject intentData)) -{ - setEnv (env); - - #if JUCE_PUSH_NOTIFICATIONS && JUCE_MODULE_AVAILABLE_juce_gui_extra - if (juce_handleNotificationIntent ((void *)intentData)) - return; - - // Add other functions processing intents here as needed. - #else - ignoreUnused (intentData); - #endif -} - -#if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) -JUCE_JNI_CALLBACK (JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME, firebaseInstanceIdTokenRefreshed, void, (JNIEnv* env, jobject /*activity*/, jstring token)) -{ - setEnv (env); - - #if JUCE_MODULE_AVAILABLE_juce_gui_extra - juce_firebaseDeviceNotificationsTokenRefreshed (token); - #else - ignoreUnused (token); - #endif -} - -JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessageReceived, void, (JNIEnv* env, jobject /*activity*/, jobject remoteMessage)) -{ - setEnv (env); - - #if JUCE_MODULE_AVAILABLE_juce_gui_extra - juce_firebaseRemoteNotificationReceived (remoteMessage); - #else - ignoreUnused (remoteMessage); - #endif -} - -JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessagesDeleted, void, (JNIEnv* env, jobject /*activity*/)) -{ - setEnv (env); - - #if JUCE_MODULE_AVAILABLE_juce_gui_extra - juce_firebaseRemoteMessagesDeleted(); - #endif -} - -JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessageSent, void, (JNIEnv* env, jobject /*activity*/, jstring messageId)) -{ - setEnv (env); - - #if JUCE_MODULE_AVAILABLE_juce_gui_extra - juce_firebaseRemoteMessageSent (messageId); - #else - ignoreUnused (messageId); - #endif -} +DECLARE_JNI_CLASS (AndroidLayoutParams, "android/view/ViewGroup$LayoutParams") +#undef JNI_CLASS_MEMBERS -JUCE_JNI_CALLBACK (JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME, firebaseRemoteMessageSendError, void, (JNIEnv* env, jobject /*activity*/, jstring messageId, jstring error)) -{ - setEnv (env); +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (addView, "addView", "(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V") \ + METHOD (removeView, "removeView", "(Landroid/view/View;)V") \ + METHOD (updateViewLayout, "updateViewLayout", "(Landroid/view/View;Landroid/view/ViewGroup$LayoutParams;)V") - #if JUCE_MODULE_AVAILABLE_juce_gui_extra - juce_firebaseRemoteMessageSendError (messageId, error); - #else - ignoreUnused (messageId, error); - #endif -} -#endif +DECLARE_JNI_CLASS (AndroidViewManager, "android/view/ViewManager") +#undef JNI_CLASS_MEMBERS //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (drawBitmap, "drawBitmap", "([IIIFFIIZLandroid/graphics/Paint;)V") \ - METHOD (getClipBounds, "getClipBounds", "()Landroid/graphics/Rect;") +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (create, "", "(IIIIIII)V") \ + FIELD (gravity, "gravity", "I") \ + FIELD (windowAnimations, "windowAnimations", "I") -DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas") +DECLARE_JNI_CLASS (AndroidWindowManagerLayoutParams, "android/view/WindowManager$LayoutParams") #undef JNI_CLASS_MEMBERS //============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (setViewName, "setViewName", "(Ljava/lang/String;)V") \ - METHOD (setVisible, "setVisible", "(Z)V") \ - METHOD (isVisible, "isVisible", "()Z") \ - METHOD (containsPoint, "containsPoint", "(II)Z") \ - METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ - METHOD (setSystemUiVisibilityCompat, "setSystemUiVisibilityCompat", "(I)V") \ - -DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView") +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (getDecorView, "getDecorView", "()Landroid/view/View;") \ + METHOD (setFlags, "setFlags", "(II)V") + +DECLARE_JNI_CLASS (AndroidWindow, "android/view/Window") #undef JNI_CLASS_MEMBERS @@ -221,18 +250,73 @@ class AndroidComponentPeer : public ComponentPeer, private Timer { public: - AndroidComponentPeer (Component& comp, const int windowStyleFlags) + AndroidComponentPeer (Component& comp, const int windowStyleFlags, void* nativeViewHandle) : ComponentPeer (comp, windowStyleFlags), fullScreen (false), navBarsHidden (false), sizeAllocated (0), scale ((float) Desktop::getInstance().getDisplays().getMainDisplay().scale) { + auto* env = getEnv(); + // NB: must not put this in the initialiser list, as it invokes a callback, // which will fail if the peer is only half-constructed. - view = GlobalRef (android.activity.callObjectMethod (JuceAppActivity.createNewView, - (jboolean) component.isOpaque(), - (jlong) this)); + view = GlobalRef (LocalRef (env->NewObject (ComponentPeerView, ComponentPeerView.create, + getAppContext().get(), (jboolean) component.isOpaque(), + (jlong) this))); + + if (nativeViewHandle != nullptr) + { + viewGroupIsWindow = false; + + // we don't know if the user is holding on to a local ref to this, so + // explicitly create a new one + auto nativeView = LocalRef(env->NewLocalRef(static_cast (nativeViewHandle))); + + if (env->IsInstanceOf (nativeView.get(), AndroidActivity)) + { + viewGroup = GlobalRef (nativeView); + env->CallVoidMethod (viewGroup.get(), AndroidActivity.setContentView, view.get()); + } + else if (env->IsInstanceOf (nativeView.get(), AndroidViewGroup)) + { + viewGroup = GlobalRef (nativeView); + LocalRef layoutParams (env->NewObject (AndroidLayoutParams, AndroidLayoutParams.create, -2, -2)); + + env->CallVoidMethod (view.get(), AndroidView.setLayoutParams, layoutParams.get()); + env->CallVoidMethod ((jobject) viewGroup.get(), AndroidViewGroup.addView, view.get()); + } + else + { + // the native handle you passed as a second argument to Component::addToDesktop must + // either be an Activity or a ViewGroup + jassertfalse; + } + } + else + { + viewGroupIsWindow = true; + + LocalRef viewLayoutParams (env->NewObject (AndroidLayoutParams, AndroidLayoutParams.create, -2, -2)); + + env->CallVoidMethod (view.get(), AndroidView.setLayoutParams, viewLayoutParams.get()); + + Rectangle physicalBounds = comp.getBoundsInParent() * scale; + + view.callVoidMethod (AndroidView.layout, + physicalBounds.getX(), physicalBounds.getY(), physicalBounds.getRight(), physicalBounds.getBottom()); + + LocalRef windowLayoutParams (env->NewObject (AndroidWindowManagerLayoutParams, AndroidWindowManagerLayoutParams.create, + physicalBounds.getWidth(), physicalBounds.getHeight(), + physicalBounds.getX(), physicalBounds.getY(), + TYPE_APPLICATION, FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_NO_LIMITS, + component.isOpaque() ? PIXEL_FORMAT_OPAQUE : PIXEL_FORMAT_TRANSPARENT)); + env->SetIntField (windowLayoutParams.get(), AndroidWindowManagerLayoutParams.gravity, GRAVITY_LEFT | GRAVITY_TOP); + env->SetIntField (windowLayoutParams.get(), AndroidWindowManagerLayoutParams.windowAnimations, 0x01030000 /* android.R.style.Animation */); + viewGroup = GlobalRef (LocalRef (env->CallObjectMethod (getCurrentActivity().get(), AndroidContext.getSystemService, javaString ("window").get()))); + + env->CallVoidMethod (viewGroup.get(), AndroidViewManager.addView, view.get(), windowLayoutParams.get()); + } if (isFocused()) handleFocusGain(); @@ -240,30 +324,40 @@ public: ~AndroidComponentPeer() { + auto* env = getEnv(); + + env->CallVoidMethod (view, ComponentPeerView.clear); + frontWindow = nullptr; + if (MessageManager::getInstance()->isThisTheMessageThread()) { - frontWindow = nullptr; - android.activity.callVoidMethod (JuceAppActivity.deleteView, view.get()); + if (env->IsInstanceOf (viewGroup.get(), AndroidActivity)) + env->CallVoidMethod (viewGroup.get(), AndroidActivity.setContentView, nullptr); + else + env->CallVoidMethod (viewGroup.get(), AndroidViewManager.removeView, view.get()); } else { struct ViewDeleter : public CallbackMessage { - ViewDeleter (const GlobalRef& view_) : view (view_) {} + ViewDeleter (const GlobalRef& view_, const GlobalRef& viewGroup_) : view (view_), group (viewGroup_) {} void messageCallback() override { - android.activity.callVoidMethod (JuceAppActivity.deleteView, view.get()); + auto* callbackEnv = getEnv(); + + if (callbackEnv->IsInstanceOf (group.get(), AndroidActivity)) + callbackEnv->CallVoidMethod (group.get(), AndroidActivity.setContentView, nullptr); + else + callbackEnv->CallVoidMethod (group.get(), AndroidViewManager.removeView, view.get()); } private: - GlobalRef view; + GlobalRef view, group; }; - (new ViewDeleter (view))->post(); + (new ViewDeleter (view, viewGroup))->post(); } - - view.clear(); } void* getNativeHandle() const override @@ -309,9 +403,25 @@ public: if (MessageManager::getInstance()->isThisTheMessageThread()) { + auto* env = getEnv(); + fullScreen = isNowFullScreen; - view.callVoidMethod (AndroidView.layout, - r.getX(), r.getY(), r.getRight(), r.getBottom()); + + { + view.callVoidMethod (AndroidView.layout, + r.getX(), r.getY(), r.getRight(), r.getBottom()); + + if (viewGroup != nullptr && viewGroupIsWindow) + { + LocalRef windowLayoutParams (env->NewObject (AndroidWindowManagerLayoutParams, AndroidWindowManagerLayoutParams.create, + r.getWidth(), r.getHeight(), + r.getX(), r.getY(), + TYPE_APPLICATION, FLAG_NOT_TOUCH_MODAL | FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_NO_LIMITS, + component.isOpaque() ? PIXEL_FORMAT_OPAQUE : PIXEL_FORMAT_TRANSPARENT)); + env->SetIntField (windowLayoutParams.get(), AndroidWindowManagerLayoutParams.gravity, 0x3 /* LEFT */ | 0x30 /* TOP */); + env->CallVoidMethod (viewGroup.get(), AndroidViewManager.updateViewLayout, view.get(), windowLayoutParams.get()); + } + } } else { @@ -337,10 +447,12 @@ public: Rectangle getBounds() const override { - return (Rectangle (view.callIntMethod (AndroidView.getLeft), - view.callIntMethod (AndroidView.getTop), - view.callIntMethod (AndroidView.getWidth), - view.callIntMethod (AndroidView.getHeight)) / scale).toNearestInt(); + Rectangle r (view.callIntMethod (AndroidView.getLeft), + view.callIntMethod (AndroidView.getTop), + view.callIntMethod (AndroidView.getWidth), + view.callIntMethod (AndroidView.getHeight)); + + return r / scale; } void handleScreenSizeChange() override @@ -351,20 +463,28 @@ public: setFullScreen (true); } - Point getScreenPosition() const + Point getScreenPosition() const { - return Point (view.callIntMethod (AndroidView.getLeft), - view.callIntMethod (AndroidView.getTop)) / scale; + auto* env = getEnv(); + + LocalRef position (env->NewIntArray (2)); + env->CallVoidMethod (view.get(), AndroidView.getLocationOnScreen, position.get()); + + jint* const screenPosition = env->GetIntArrayElements (position.get(), 0); + Point pos (screenPosition[0], screenPosition[1]); + env->ReleaseIntArrayElements (position.get(), screenPosition, 0); + + return pos; } Point localToGlobal (Point relativePosition) override { - return relativePosition + getScreenPosition(); + return relativePosition + (getScreenPosition().toFloat() / scale); } Point globalToLocal (Point screenPosition) override { - return screenPosition - getScreenPosition(); + return screenPosition - (getScreenPosition().toFloat() / scale); } void setMinimised (bool /*shouldBeMinimised*/) override @@ -498,8 +618,8 @@ public: //============================================================================== void handleMouseDownCallback (int index, Point sysPos, int64 time) { - Point pos = sysPos / scale; - lastMousePos = localToGlobal (pos); + lastMousePos = sysPos / scale; + Point pos = globalToLocal (lastMousePos); // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, ModifierKeys::currentModifiers.withoutMouseButtons(), @@ -509,10 +629,10 @@ public: handleMouseDragCallback (index, sysPos, time); } - void handleMouseDragCallback (int index, Point pos, int64 time) + void handleMouseDragCallback (int index, Point sysPos, int64 time) { - pos /= scale; - lastMousePos = localToGlobal (pos); + lastMousePos = sysPos / scale; + Point pos = globalToLocal (lastMousePos); jassert (index < 64); touchesDown = (touchesDown | (1 << (index & 63))); @@ -521,10 +641,10 @@ public: MouseInputSource::invalidPressure, MouseInputSource::invalidOrientation, time, {}, index); } - void handleMouseUpCallback (int index, Point pos, int64 time) + void handleMouseUpCallback (int index, Point sysPos, int64 time) { - pos /= scale; - lastMousePos = localToGlobal (pos); + lastMousePos = sysPos / scale; + Point pos = globalToLocal (lastMousePos); jassert (index < 64); touchesDown = (touchesDown & ~(1 << (index & 63))); @@ -629,9 +749,11 @@ public: } //============================================================================== - void handlePaintCallback (JNIEnv* env, jobject canvas, jobject paint) + void handlePaintCallback (jobject canvas, jobject paint) { - jobject rect = env->CallObjectMethod (canvas, CanvasMinimal.getClipBounds); + auto* env = getEnv(); + + jobject rect = env->CallObjectMethod (canvas, AndroidCanvas.getClipBounds); const int left = env->GetIntField (rect, AndroidRect.left); const int top = env->GetIntField (rect, AndroidRect.top); const int right = env->GetIntField (rect, AndroidRect.right); @@ -645,7 +767,7 @@ public: { buffer.clear(); sizeAllocated = sizeNeeded; - buffer = GlobalRef (env->NewIntArray (sizeNeeded)); + buffer = GlobalRef (LocalRef ((jobject) env->NewIntArray (sizeNeeded))); } else if (sizeNeeded == 0) { @@ -668,7 +790,7 @@ public: env->ReleaseIntArrayElements ((jintArray) buffer.get(), dest, 0); - env->CallVoidMethod (canvas, CanvasMinimal.drawBitmap, (jintArray) buffer.get(), 0, clip.getWidth(), + env->CallVoidMethod (canvas, AndroidCanvas.drawBitmap, (jintArray) buffer.get(), 0, clip.getWidth(), (jfloat) clip.getX(), (jfloat) clip.getY(), clip.getWidth(), clip.getHeight(), true, paint); } @@ -723,15 +845,87 @@ public: static Point lastMousePos; static int64 touchesDown; + //============================================================================== + struct StartupActivityCallbackListener : ActivityLifecycleCallbacks + { + void onActivityStarted (jobject /*activity*/) override + { + auto* env = getEnv(); + LocalRef appContext (getAppContext()); + + if (appContext.get() != nullptr) + { + + env->CallVoidMethod (appContext.get(), + AndroidApplication.unregisterActivityLifecycleCallbacks, + activityCallbackListener.get()); + clear(); + activityCallbackListener.clear(); + + const_cast (Desktop::getInstance().getDisplays()).refresh(); + } + } + }; + private: //============================================================================== - GlobalRef view; + GlobalRef view, viewGroup; + bool viewGroupIsWindow = false; GlobalRef buffer; bool fullScreen; bool navBarsHidden; int sizeAllocated; float scale; + + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (create, "", "(Landroid/content/Context;ZJ)V") \ + METHOD (clear, "clear", "()V") \ + METHOD (setViewName, "setViewName", "(Ljava/lang/String;)V") \ + METHOD (setVisible, "setVisible", "(Z)V") \ + METHOD (isVisible, "isVisible", "()Z") \ + METHOD (containsPoint, "containsPoint", "(II)Z") \ + METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ + METHOD (setSystemUiVisibilityCompat, "setSystemUiVisibilityCompat", "(I)V") \ + CALLBACK (handlePaintJni, "handlePaint", "(JLandroid/graphics/Canvas;Landroid/graphics/Paint;)V") \ + CALLBACK (handleMouseDownJni, "handleMouseDown", "(JIFFJ)V") \ + CALLBACK (handleMouseDragJni, "handleMouseDrag", "(JIFFJ)V") \ + CALLBACK (handleMouseUpJni, "handleMouseUp", "(JIFFJ)V") \ + CALLBACK (handleKeyDownJni, "handleKeyDown", "(JII)V") \ + CALLBACK (handleKeyUpJni, "handleKeyUp", "(JII)V") \ + CALLBACK (handleBackButtonJni, "handleBackButton", "(J)V") \ + CALLBACK (handleKeyboardHiddenJni, "handleKeyboardHidden", "(J)V") \ + CALLBACK (viewSizeChangedJni, "viewSizeChanged", "(J)V") \ + CALLBACK (focusChangedJni, "focusChanged", "(JZ)V") \ + CALLBACK (handleAppPausedJni, "handleAppPaused", "(J)V") \ + CALLBACK (handleAppResumedJni, "handleAppResumed", "(J)V") \ + + DECLARE_JNI_CLASS_WITH_BYTECODE (ComponentPeerView, "com/roli/juce/ComponentPeerView", 16, javaComponentPeerView, sizeof(javaComponentPeerView)) + #undef JNI_CLASS_MEMBERS + + static void JNICALL handlePaintJni (JNIEnv*, jobject /*view*/, jlong host, jobject canvas, jobject paint) { if (auto* myself = reinterpret_cast (host)) myself->handlePaintCallback (canvas, paint); } + static void JNICALL handleMouseDownJni (JNIEnv*, jobject /*view*/, jlong host, jint i, jfloat x, jfloat y, jlong time) { if (auto* myself = reinterpret_cast (host)) myself->handleMouseDownCallback (i, Point ((float) x, (float) y), (int64) time); } + static void JNICALL handleMouseDragJni (JNIEnv*, jobject /*view*/, jlong host, jint i, jfloat x, jfloat y, jlong time) { if (auto* myself = reinterpret_cast (host)) myself->handleMouseDragCallback (i, Point ((float) x, (float) y), (int64) time); } + static void JNICALL handleMouseUpJni (JNIEnv*, jobject /*view*/, jlong host, jint i, jfloat x, jfloat y, jlong time) { if (auto* myself = reinterpret_cast (host)) myself->handleMouseUpCallback (i, Point ((float) x, (float) y), (int64) time); } + static void JNICALL viewSizeChangedJni (JNIEnv*, jobject /*view*/, jlong host) { if (auto* myself = reinterpret_cast (host)) myself->handleMovedOrResized(); } + static void JNICALL focusChangedJni (JNIEnv*, jobject /*view*/, jlong host, jboolean hasFocus) { if (auto* myself = reinterpret_cast (host)) myself->handleFocusChangeCallback (hasFocus); } + static void JNICALL handleKeyDownJni (JNIEnv*, jobject /*view*/, jlong host, jint k, jint kc) { if (auto* myself = reinterpret_cast (host)) myself->handleKeyDownCallback ((int) k, (int) kc); } + static void JNICALL handleKeyUpJni (JNIEnv*, jobject /*view*/, jlong host, jint k, jint kc) { if (auto* myself = reinterpret_cast (host)) myself->handleKeyUpCallback ((int) k, (int) kc); } + static void JNICALL handleBackButtonJni (JNIEnv*, jobject /*view*/, jlong host) { if (auto* myself = reinterpret_cast (host)) myself->handleBackButtonCallback(); } + static void JNICALL handleKeyboardHiddenJni (JNIEnv*, jobject /*view*/, jlong host) { if (auto* myself = reinterpret_cast (host)) myself->handleKeyboardHiddenCallback(); } + static void JNICALL handleAppPausedJni (JNIEnv*, jobject /*view*/, jlong host) { if (auto* myself = reinterpret_cast (host)) myself->handleAppPausedCallback(); } + static void JNICALL handleAppResumedJni (JNIEnv*, jobject /*view*/, jlong host) { if (auto* myself = reinterpret_cast (host)) myself->handleAppResumedCallback(); } + + //============================================================================== + friend class Displays; static AndroidComponentPeer* frontWindow; + static GlobalRef activityCallbackListener; + + //============================================================================== + static constexpr int GRAVITY_LEFT = 0x3, GRAVITY_TOP = 0x30; + static constexpr int TYPE_APPLICATION = 0x2; + static constexpr int FLAG_NOT_TOUCH_MODAL = 0x20, FLAG_LAYOUT_IN_SCREEN = 0x100, FLAG_LAYOUT_NO_LIMITS = 0x200; + static constexpr int PIXEL_FORMAT_OPAQUE = -1, PIXEL_FORMAT_TRANSPARENT = -2; struct PreallocatedImage : public ImagePixelData { @@ -790,33 +984,13 @@ private: Point AndroidComponentPeer::lastMousePos; int64 AndroidComponentPeer::touchesDown = 0; AndroidComponentPeer* AndroidComponentPeer::frontWindow = nullptr; +GlobalRef AndroidComponentPeer::activityCallbackListener; +AndroidComponentPeer::ComponentPeerView_Class AndroidComponentPeer::ComponentPeerView; //============================================================================== -#define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation) \ - JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024ComponentPeerView), javaMethodName, returnType, params) \ - { \ - setEnv (env); \ - if (AndroidComponentPeer* peer = (AndroidComponentPeer*) (pointer_sized_uint) host) \ - peer->juceMethodInvocation; \ - } - -JUCE_VIEW_CALLBACK (void, handlePaint, (JNIEnv* env, jobject /*view*/, jlong host, jobject canvas, jobject paint), handlePaintCallback (env, canvas, paint)) -JUCE_VIEW_CALLBACK (void, handleMouseDown, (JNIEnv* env, jobject /*view*/, jlong host, jint i, jfloat x, jfloat y, jlong time), handleMouseDownCallback (i, Point ((float) x, (float) y), (int64) time)) -JUCE_VIEW_CALLBACK (void, handleMouseDrag, (JNIEnv* env, jobject /*view*/, jlong host, jint i, jfloat x, jfloat y, jlong time), handleMouseDragCallback (i, Point ((float) x, (float) y), (int64) time)) -JUCE_VIEW_CALLBACK (void, handleMouseUp, (JNIEnv* env, jobject /*view*/, jlong host, jint i, jfloat x, jfloat y, jlong time), handleMouseUpCallback (i, Point ((float) x, (float) y), (int64) time)) -JUCE_VIEW_CALLBACK (void, viewSizeChanged, (JNIEnv* env, jobject /*view*/, jlong host), handleMovedOrResized()) -JUCE_VIEW_CALLBACK (void, focusChanged, (JNIEnv* env, jobject /*view*/, jlong host, jboolean hasFocus), handleFocusChangeCallback (hasFocus)) -JUCE_VIEW_CALLBACK (void, handleKeyDown, (JNIEnv* env, jobject /*view*/, jlong host, jint k, jint kc), handleKeyDownCallback ((int) k, (int) kc)) -JUCE_VIEW_CALLBACK (void, handleKeyUp, (JNIEnv* env, jobject /*view*/, jlong host, jint k, jint kc), handleKeyUpCallback ((int) k, (int) kc)) -JUCE_VIEW_CALLBACK (void, handleBackButton, (JNIEnv* env, jobject /*view*/, jlong host), handleBackButtonCallback()) -JUCE_VIEW_CALLBACK (void, handleKeyboardHidden, (JNIEnv* env, jobject /*view*/, jlong host), handleKeyboardHiddenCallback()) -JUCE_VIEW_CALLBACK (void, handleAppPaused, (JNIEnv* env, jobject /*view*/, jlong host), handleAppPausedCallback()) -JUCE_VIEW_CALLBACK (void, handleAppResumed, (JNIEnv* env, jobject /*view*/, jlong host), handleAppResumedCallback()) - -//============================================================================== -ComponentPeer* Component::createNewPeer (int styleFlags, void*) +ComponentPeer* Component::createNewPeer (int styleFlags, void* nativeWindow) { - return new AndroidComponentPeer (*this, styleFlags); + return new AndroidComponentPeer (*this, styleFlags, nativeWindow); } //============================================================================== @@ -842,7 +1016,9 @@ Desktop::DisplayOrientation Desktop::getCurrentOrientation() const JNIEnv* env = getEnv(); LocalRef windowServiceString (javaString ("window")); - LocalRef windowManager = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getSystemService, windowServiceString.get())); + + + LocalRef windowManager = LocalRef (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, windowServiceString.get())); if (windowManager.get() != 0) { @@ -896,15 +1072,16 @@ bool KeyPress::isKeyCurrentlyDown (const int /*keyCode*/) JUCE_API void JUCE_CALLTYPE Process::hide() { - if (android.activity.callBooleanMethod (JuceAppActivity.moveTaskToBack, true) == 0) - { - auto* env = getEnv(); + auto* env = getEnv(); + LocalRef currentActivity (getCurrentActivity().get()); - GlobalRef intent (env->NewObject (AndroidIntent, AndroidIntent.constructor)); + if (env->CallBooleanMethod (currentActivity.get(), AndroidActivity.moveTaskToBack, true) == 0) + { + GlobalRef intent (LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructor))); env->CallObjectMethod (intent, AndroidIntent.setAction, javaString ("android.intent.action.MAIN") .get()); env->CallObjectMethod (intent, AndroidIntent.addCategory, javaString ("android.intent.category.HOME").get()); - android.activity.callVoidMethod (JuceAppActivity.startActivity, intent.get()); + env->CallVoidMethod (currentActivity.get(), AndroidContext.startActivity, intent.get()); } } @@ -914,13 +1091,123 @@ JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { return true; } JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() {} //============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (show, "show", "()V") + +DECLARE_JNI_CLASS (AndroidDialog, "android/app/Dialog") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (construct, "", "(Landroid/content/Context;)V") \ + METHOD (create, "create", "()Landroid/app/AlertDialog;") \ + METHOD (setTitle, "setTitle", "(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;") \ + METHOD (setMessage, "setMessage", "(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;") \ + METHOD (setCancelable, "setCancelable", "(Z)Landroid/app/AlertDialog$Builder;") \ + METHOD (setOnCancelListener, "setOnCancelListener", "(Landroid/content/DialogInterface$OnCancelListener;)Landroid/app/AlertDialog$Builder;") \ + METHOD (setPositiveButton, "setPositiveButton", "(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;") \ + METHOD (setNegativeButton, "setNegativeButton", "(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;") \ + METHOD (setNeutralButton, "setNeutralButton", "(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;") + +DECLARE_JNI_CLASS (AndroidAlertDialogBuilder, "android/app/AlertDialog$Builder") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (dismiss, "dismiss", "()V") + +DECLARE_JNI_CLASS (AndroidDialogInterface, "android/content/DialogInterface") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + +DECLARE_JNI_CLASS (AndroidDialogOnClickListener, "android/content/DialogInterface$OnClickListener") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +class DialogListener : public juce::AndroidInterfaceImplementer +{ +public: + DialogListener (ModalComponentManager::Callback* callbackToUse, int resultToUse) + : callback (callbackToUse), result (resultToUse) + {} + + void onResult (jobject dialog) + { + auto* env = getEnv(); + env->CallVoidMethod (dialog, AndroidDialogInterface.dismiss); + + if (callback != nullptr) + callback->modalStateFinished (result); + + callback = nullptr; + } + +private: + jobject invoke (jobject proxy, jobject method, jobjectArray args) override + { + auto* env = getEnv(); + auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); + + if (methodName == "onCancel" || methodName == "onClick") + { + onResult (env->GetObjectArrayElement (args, 0)); + return nullptr; + } + + // invoke base class + return AndroidInterfaceImplementer::invoke (proxy, method, args); + } + + std::unique_ptr callback; + int result; +}; + +//============================================================================== +static void createAndroidDialog (const String& title, const String& message, + ModalComponentManager::Callback* callback, + const String& positiveButton = {}, const String& negativeButton = {}, + const String& neutralButton = {}) +{ + auto* env = getEnv(); + + LocalRef builder (env->NewObject (AndroidAlertDialogBuilder, AndroidAlertDialogBuilder.construct, getMainActivity().get())); + + builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setTitle, javaString (title).get())); + builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setMessage, javaString (message).get())); + builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setCancelable, true)); + + builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setOnCancelListener, + CreateJavaInterface (new DialogListener (callback, 0), + "android/content/DialogInterface$OnCancelListener").get())); + + auto positiveButtonText = positiveButton.isEmpty() ? String ("OK") : positiveButton; + + builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setPositiveButton, + javaString (positiveButtonText).get(), + CreateJavaInterface (new DialogListener (callback, positiveButton.isEmpty() ? 0 : 1), + "android/content/DialogInterface$OnClickListener").get())); + + if (negativeButton.isNotEmpty()) + builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNegativeButton, + javaString (negativeButton).get(), + CreateJavaInterface (new DialogListener (callback, neutralButton.isEmpty() ? 0 : 2), + "android/content/DialogInterface$OnClickListener").get())); + + if (neutralButton.isNotEmpty()) + builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNegativeButton, + javaString (neutralButton).get(), + CreateJavaInterface (new DialogListener (callback, 0), + "android/content/DialogInterface$OnClickListener").get())); + + LocalRef dialog (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.create)); + env->CallVoidMethod (dialog.get(), AndroidDialog.show); +} + void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { - android.activity.callVoidMethod (JuceAppActivity.showMessageBox, javaString (title).get(), - javaString (message).get(), (jlong) (pointer_sized_int) callback); + createAndroidDialog (title, message, callback); } bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType /*iconType*/, @@ -930,9 +1217,7 @@ bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType { jassert (callback != nullptr); // on android, all alerts must be non-modal!! - android.activity.callVoidMethod (JuceAppActivity.showOkCancelBox, javaString (title).get(), - javaString (message).get(), (jlong) (pointer_sized_int) callback, - javaString (TRANS ("OK")).get(), javaString (TRANS ("Cancel")).get()); + createAndroidDialog (title, message, callback, "OK", "Cancel"); return false; } @@ -943,8 +1228,7 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconTy { jassert (callback != nullptr); // on android, all alerts must be non-modal!! - android.activity.callVoidMethod (JuceAppActivity.showYesNoCancelBox, javaString (title).get(), - javaString (message).get(), (jlong) (pointer_sized_int) callback); + createAndroidDialog (title, message, callback, "Yes", "No", "Cancel"); return 0; } @@ -955,33 +1239,36 @@ int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType /*i { jassert (callback != nullptr); // on android, all alerts must be non-modal!! - android.activity.callVoidMethod (JuceAppActivity.showOkCancelBox, javaString (title).get(), - javaString (message).get(), (jlong) (pointer_sized_int) callback, - javaString (TRANS ("Yes")).get(), javaString (TRANS ("No")).get()); + createAndroidDialog (title, message, callback, "Yes", "No"); return 0; } -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, alertDismissed, void, (JNIEnv* env, jobject /*activity*/, - jlong callbackAsLong, jint result)) +//============================================================================== +static bool androidScreenSaverEnabled = false; + +void Desktop::setScreenSaverEnabled (const bool shouldEnable) { - setEnv (env); + constexpr auto FLAG_KEEP_SCREEN_ON = 0x80; - if (ModalComponentManager::Callback* callback = (ModalComponentManager::Callback*) callbackAsLong) + if (shouldEnable != androidScreenSaverEnabled) { - callback->modalStateFinished (result); - delete callback; - } -} + LocalRef activity (getMainActivity()); -//============================================================================== -void Desktop::setScreenSaverEnabled (const bool isEnabled) -{ - android.activity.callVoidMethod (JuceAppActivity.setScreenSaver, isEnabled); + if (activity != nullptr) + { + auto* env = getEnv(); + + LocalRef mainWindow (env->CallObjectMethod (activity.get(), AndroidActivity.getWindow)); + env->CallVoidMethod(mainWindow.get(), AndroidWindow.setFlags, shouldEnable ? FLAG_KEEP_SCREEN_ON : 0, FLAG_KEEP_SCREEN_ON); + } + + androidScreenSaverEnabled = shouldEnable; + } } bool Desktop::isScreenSaverEnabled() { - return android.activity.callBooleanMethod (JuceAppActivity.getScreenSaver); + return androidScreenSaverEnabled; } //============================================================================== @@ -1023,8 +1310,10 @@ static jint getAndroidOrientationFlag (int orientations) noexcept void Desktop::allowedOrientationsChanged() { - android.activity.callVoidMethod (JuceAppActivity.setRequestedOrientation, - getAndroidOrientationFlag (allowedOrientations)); + LocalRef activity (getMainActivity()); + + if (activity != nullptr) + getEnv()->CallVoidMethod (activity.get(), AndroidActivity.setRequestedOrientation, getAndroidOrientationFlag (allowedOrientations)); } //============================================================================== @@ -1033,31 +1322,165 @@ bool juce_areThereAnyAlwaysOnTopWindows() return false; } +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (create, "", "()V") \ + FIELD (density, "density", "F") \ + FIELD (widthPixels, "widthPixels", "I") \ + FIELD (heightPixels, "heightPixels", "I") + +DECLARE_JNI_CLASS (AndroidDisplayMetrics, "android/util/DisplayMetrics") +#undef JNI_CLASS_MEMBERS + +//============================================================================== +class LayoutChangeListener : public juce::AndroidInterfaceImplementer +{ +public: + virtual void onLayoutChange (LocalRef view, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) = 0; + +private: + jobject invoke (jobject proxy, jobject method, jobjectArray args) override + { + auto* env = getEnv(); + auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); + + if (methodName == "onLayoutChange") + { + jassert (env->GetArrayLength (args) == 9); + + LocalRef view (env->GetObjectArrayElement (args, 0)); + int dims[8]; + + for (int i = 1; i < 9; ++i) + { + LocalRef integer (env->GetObjectArrayElement (args, i)); + dims[i - 1] = env->CallIntMethod (integer.get(), JavaInteger.intValue); + } + + onLayoutChange (std::move (view), dims[0], dims[1], dims[2], dims[3], + dims[4], dims[5], dims[6], dims[7]); + + return nullptr; + } + + // invoke base class + return AndroidInterfaceImplementer::invoke (proxy, method, args); + } + + std::unique_ptr callback; +}; + +//============================================================================== +class MainActivityWindowLayoutListener : public LayoutChangeListener +{ +public: + void onLayoutChange (LocalRef /*view*/, int left, int top, int right, int bottom, + int oldLeft, int oldTop, int oldRight, int oldBottom) override + { + auto newBounds = Rectangle::leftTopRightBottom (left, top, right, bottom); + auto oldBounds = Rectangle::leftTopRightBottom (oldLeft, oldTop, oldRight, oldBottom); + + if (newBounds != oldBounds) + { + auto& displays = Desktop::getInstance().getDisplays(); + auto& mainDisplay = displays.getMainDisplay(); + + Rectangle userArea = newBounds / mainDisplay.scale; + + if (userArea != mainDisplay.userArea) + const_cast (displays).refresh(); + } + } +}; + //============================================================================== void Displays::findDisplays (float masterScale) { + auto* env = getEnv(); + + LocalRef usableSize (env->NewObject (AndroidPoint, AndroidPoint.create, 0, 0)); + LocalRef windowServiceString (javaString ("window")); + LocalRef displayMetrics (env->NewObject (AndroidDisplayMetrics, AndroidDisplayMetrics.create)); + LocalRef windowManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, windowServiceString.get())); + LocalRef display (env->CallObjectMethod (windowManager, AndroidWindowManager.getDefaultDisplay)); + + jmethodID getRealMetricsMethod = env->GetMethodID (AndroidDisplay, "getRealMetrics", "(Landroid/util/DisplayMetrics;)V"); + + if (getRealMetricsMethod != 0) + env->CallVoidMethod (display.get(), getRealMetricsMethod, displayMetrics.get()); + else + env->CallVoidMethod (display.get(), AndroidDisplay.getMetrics, displayMetrics.get()); + + env->CallVoidMethod (display.get(), AndroidDisplay.getSize, usableSize.get()); + Display d; d.isMain = true; - d.dpi = android.dpi; - d.scale = masterScale * (d.dpi / 150.); - d.userArea = d.totalArea = Rectangle (android.screenWidth, - android.screenHeight) / d.scale; + d.scale = env->GetFloatField (displayMetrics.get(), AndroidDisplayMetrics.density); + d.dpi = (d.scale * 160.f); + d.scale *= masterScale; - displays.add (d); -} + d.totalArea = Rectangle (env->GetIntField (displayMetrics.get(), AndroidDisplayMetrics.widthPixels), + env->GetIntField (displayMetrics.get(), AndroidDisplayMetrics.heightPixels)) / d.scale; -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, setScreenSize, void, (JNIEnv* env, jobject /*activity*/, - jint screenWidth, jint screenHeight, - jint dpi)) -{ - setEnv (env); + d.userArea = Rectangle (env->GetIntField (usableSize.get(), AndroidPoint.x), + env->GetIntField (usableSize.get(), AndroidPoint.y)) / d.scale; + + // unfortunately usableSize still contains the nav bar + // the best workaround is to try to get the size of the top-level view of + // the main activity + LocalRef activity (getMainActivity()); + + if (activity != nullptr) + { + LocalRef mainWindow (env->CallObjectMethod (activity.get(), AndroidActivity.getWindow)); + LocalRef decorView (env->CallObjectMethod (mainWindow.get(), AndroidWindow.getDecorView)); + LocalRef contentView (env->CallObjectMethod (decorView.get(), AndroidView.findViewById, 0x01020002 /* android.R.id.content */)); + + if (contentView != nullptr) + { + Rectangle activityArea (env->CallIntMethod (contentView.get(), AndroidView.getLeft), + env->CallIntMethod (contentView.get(), AndroidView.getTop), + env->CallIntMethod (contentView.get(), AndroidView.getWidth), + env->CallIntMethod (contentView.get(), AndroidView.getHeight)); + + if (! activityArea.isEmpty()) + d.userArea = activityArea / d.scale; - android.screenWidth = screenWidth; - android.screenHeight = screenHeight; - android.dpi = dpi; + static bool hasAddedMainActivityListener = false; - const_cast (Desktop::getInstance().getDisplays()).refresh(); + if (! hasAddedMainActivityListener) + { + hasAddedMainActivityListener = true; + + env->CallVoidMethod (contentView.get(), AndroidView.addOnLayoutChangeListener, + CreateJavaInterface (new MainActivityWindowLayoutListener, + "android/view/View$OnLayoutChangeListener").get()); + } + } + } + else + { + // the main activity may have not started yet so add an activity listener + if (AndroidComponentPeer::activityCallbackListener == nullptr) + { + LocalRef appContext (getAppContext()); + + if (appContext.get() != nullptr) + { + AndroidComponentPeer::activityCallbackListener = GlobalRef (CreateJavaInterface ( + new AndroidComponentPeer::StartupActivityCallbackListener, + "android/app/Application$ActivityLifecycleCallbacks")); + + env->CallVoidMethod (appContext.get(), + AndroidApplication.registerActivityLifecycleCallbacks, + AndroidComponentPeer::activityCallbackListener.get()); + } + } + } + + displays.add (d); } //============================================================================== @@ -1094,17 +1517,34 @@ void LookAndFeel::playAlertSound() { } +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (getText, "getText", "()Ljava/lang/CharSequence;") \ + METHOD (setText, "setText", "(Ljava/lang/CharSequence;)V") + +DECLARE_JNI_CLASS (AndroidClipboardManager, "android/content/ClipboardManager") +#undef JNI_CLASS_MEMBERS + //============================================================================== void SystemClipboard::copyTextToClipboard (const String& text) { - const LocalRef t (javaString (text)); - android.activity.callVoidMethod (JuceAppActivity.setClipboardContent, t.get()); + auto* env = getEnv(); + + LocalRef clipboardManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, javaString ("clipboard").get())); + env->CallVoidMethod (clipboardManager.get(), AndroidClipboardManager.setText, javaString(text).get()); } String SystemClipboard::getTextFromClipboard() { - const LocalRef text ((jstring) android.activity.callObjectMethod (JuceAppActivity.getClipboardContent)); - return juceString (text); + auto* env = getEnv(); + + LocalRef clipboardManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, javaString ("clipboard").get())); + LocalRef charSequence (env->CallObjectMethod (clipboardManager.get(), AndroidClipboardManager.getText)); + + if (charSequence == nullptr) + return {}; + + return juceString(LocalRef ((jstring) env->CallObjectMethod(charSequence.get(), JavaCharSequence.toString))); } //============================================================================== diff --git a/modules/juce_gui_extra/embedding/juce_AndroidViewComponent.h b/modules/juce_gui_extra/embedding/juce_AndroidViewComponent.h index e9244deb9c..8234437774 100644 --- a/modules/juce_gui_extra/embedding/juce_AndroidViewComponent.h +++ b/modules/juce_gui_extra/embedding/juce_AndroidViewComponent.h @@ -46,12 +46,8 @@ class JUCE_API AndroidViewComponent : public Component { public: //============================================================================== - /** Create an initially-empty container. The optional flag should be left as - false in most of the cases. Currently it is only set to true as a workaround - for a web browser bug, where scrolling would be very slow and it would - randomly scroll in an opposite direction of scrolling. - */ - AndroidViewComponent (bool embedAsSiblingRatherThanChild = false); + /** Create an initially-empty container */ + AndroidViewComponent(); /** Destructor. */ ~AndroidViewComponent(); @@ -77,8 +73,6 @@ private: class Pimpl; std::unique_ptr pimpl; - bool embedAsSiblingRatherThanChild; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidViewComponent) }; diff --git a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h index 12c2db7949..ee9cd6157e 100644 --- a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h +++ b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h @@ -134,10 +134,10 @@ public: void visibilityChanged() override; /** @internal */ void focusGained (FocusChangeType) override; - + /** @internal */ + class Pimpl; private: //============================================================================== - class Pimpl; std::unique_ptr browser; bool blankPageShown = false, unloadPageWhenBrowserIsHidden; String lastURL; @@ -147,10 +147,6 @@ private: void reloadLastURL(); void checkWindowAssociation(); - #if JUCE_ANDROID - friend bool juce_webViewPageLoadStarted (WebBrowserComponent*, const String&); - #endif - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebBrowserComponent) }; diff --git a/modules/juce_gui_extra/native/java/com/roli/juce/JuceWebView.java b/modules/juce_gui_extra/native/java/com/roli/juce/JuceWebView.java new file mode 100644 index 0000000000..be4155769e --- /dev/null +++ b/modules/juce_gui_extra/native/java/com/roli/juce/JuceWebView.java @@ -0,0 +1,107 @@ +package com.roli.juce; + +import android.graphics.Bitmap; +import android.net.http.SslError; +import android.os.Message; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; + + +//============================================================================== +public class JuceWebView +{ + static public class Client extends WebViewClient + { + public Client (long hostToUse) + { + host = hostToUse; + } + + public void hostDeleted () + { + synchronized (hostLock) + { + host = 0; + } + } + + public void onPageFinished (WebView view, String url) + { + if (host == 0) + return; + + webViewPageLoadFinished (host, view, url); + } + + public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) + { + if (host == 0) + return; + + webViewReceivedSslError (host, view, handler, error); + } + + public void onPageStarted (WebView view, String url, Bitmap favicon) + { + if (host != 0) + webViewPageLoadStarted (host, view, url); + } + + public WebResourceResponse shouldInterceptRequest (WebView view, String url) + { + synchronized (hostLock) + { + if (host != 0) + { + boolean shouldLoad = webViewPageLoadStarted (host, view, url); + + if (shouldLoad) + return null; + } + } + + return new WebResourceResponse ("text/html", null, null); + } + + private native boolean webViewPageLoadStarted (long host, WebView view, String url); + + private native void webViewPageLoadFinished (long host, WebView view, String url); + + private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error); + + private long host; + private final Object hostLock = new Object (); + } + + static public class ChromeClient extends WebChromeClient + { + public ChromeClient (long hostToUse) + { + host = hostToUse; + } + + @Override + public void onCloseWindow (WebView window) + { + webViewCloseWindowRequest (host, window); + } + + @Override + public boolean onCreateWindow (WebView view, boolean isDialog, + boolean isUserGesture, Message resultMsg) + { + webViewCreateWindowRequest (host, view); + return false; + } + + private native void webViewCloseWindowRequest (long host, WebView view); + + private native void webViewCreateWindowRequest (long host, WebView view); + + private long host; + private final Object hostLock = new Object (); + } +} \ No newline at end of file diff --git a/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp b/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp index 7024b8a35f..82c283ff54 100644 --- a/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp +++ b/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp @@ -30,11 +30,10 @@ namespace juce class AndroidViewComponent::Pimpl : public ComponentMovementWatcher { public: - Pimpl (jobject v, Component& comp, bool makeSiblingRatherThanChild = false) + Pimpl (const LocalRef& v, Component& comp) : ComponentMovementWatcher (&comp), view (v), - owner (comp), - embedAsSiblingRatherThanChild (makeSiblingRatherThanChild) + owner (comp) { if (owner.isShowing()) componentPeerChanged(); @@ -68,7 +67,6 @@ public: if (currentPeer != peer) { removeFromParent(); - currentPeer = peer; addToParent(); @@ -91,10 +89,6 @@ public: void componentBroughtToFront (Component& comp) override { ComponentMovementWatcher::componentBroughtToFront (comp); - - // Ensure that the native component doesn't get obscured. - if (embedAsSiblingRatherThanChild) - getEnv()->CallVoidMethod (view, AndroidView.bringToFront); } Rectangle getViewBounds() const @@ -119,20 +113,7 @@ private: // NB: Assuming a parent is always of ViewGroup type auto* env = getEnv(); - if (embedAsSiblingRatherThanChild) - { - // This is a workaround for a bug in a web browser component where - // scrolling would be very slow and occasionally would scroll in - // opposite direction to dragging direction. In normal circumstances, - // the native view should be a child of peerView instead. - auto parentView = LocalRef (env->CallObjectMethod (peerView, AndroidView.getParent)); - env->CallVoidMethod (parentView, AndroidViewGroup.addView, view.get()); - } - else - { - env->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get()); - } - + env->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get()); componentMovedOrResized (false, false); } } @@ -150,15 +131,13 @@ private: } Component& owner; - bool embedAsSiblingRatherThanChild; ComponentPeer* currentPeer = nullptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) }; //============================================================================== -AndroidViewComponent::AndroidViewComponent (bool makeSiblingRatherThanChild) - : embedAsSiblingRatherThanChild (makeSiblingRatherThanChild) +AndroidViewComponent::AndroidViewComponent() { } @@ -171,7 +150,14 @@ void AndroidViewComponent::setView (void* view) pimpl.reset(); if (view != nullptr) - pimpl.reset (new Pimpl ((jobject) view, *this, embedAsSiblingRatherThanChild)); + { + // explicitly create a new local ref here so that we don't + // delete the users pointer + auto* env = getEnv(); + auto localref = LocalRef(env->NewLocalRef((jobject) view)); + + pimpl.reset (new Pimpl (localref, *this)); + } } } diff --git a/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp b/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp index bf132b647f..1af82d3a3d 100644 --- a/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp +++ b/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp @@ -27,8 +27,7 @@ namespace juce { -#if __ANDROID_API__ >= 26 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \ METHOD (enableLights, "enableLights", "(Z)V") \ METHOD (enableVibration, "enableVibration", "(Z)V") \ @@ -42,36 +41,31 @@ namespace juce METHOD (setSound, "setSound", "(Landroid/net/Uri;Landroid/media/AudioAttributes;)V") \ METHOD (setVibrationPattern, "setVibrationPattern", "([J)V") -DECLARE_JNI_CLASS (NotificationChannel, "android/app/NotificationChannel") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannel, "android/app/NotificationChannel", 26) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/CharSequence;)V") -DECLARE_JNI_CLASS (NotificationChannelGroup, "android/app/NotificationChannelGroup") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannelGroup, "android/app/NotificationChannelGroup", 26) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 19 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ FIELD (extras, "extras", "Landroid/os/Bundle;") -DECLARE_JNI_CLASS (AndroidNotification, "android/app/Notification") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidNotification, "android/app/Notification", 19) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 20 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addExtras, "addExtras", "(Landroid/os/Bundle;)Landroid/app/Notification$Action$Builder;") \ METHOD (addRemoteInput, "addRemoteInput", "(Landroid/app/RemoteInput;)Landroid/app/Notification$Action$Builder;") \ METHOD (constructor, "", "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)V") \ METHOD (build, "build", "()Landroid/app/Notification$Action;") -DECLARE_JNI_CLASS (NotificationActionBuilder, "android/app/Notification$Action$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationActionBuilder, "android/app/Notification$Action$Builder", 20) #undef JNI_CLASS_MEMBERS -#endif -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getNotification, "getNotification", "()Landroid/app/Notification;") \ METHOD (setAutoCancel, "setAutoCancel", "(Z)Landroid/app/Notification$Builder;") \ METHOD (setContentInfo, "setContentInfo", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ @@ -92,31 +86,26 @@ DECLARE_JNI_CLASS (NotificationActionBuilder, "android/app/Notification$Action$B METHOD (setVibrate, "setVibrate", "([J)Landroid/app/Notification$Builder;") \ METHOD (setWhen, "setWhen", "(J)Landroid/app/Notification$Builder;") -DECLARE_JNI_CLASS (NotificationBuilderBase, "android/app/Notification$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderBase, "android/app/Notification$Builder", 11) #undef JNI_CLASS_MEMBERS -#if __ANDROID_API__ >= 16 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addAction, "addAction", "(ILjava/lang/CharSequence;Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \ METHOD (build, "build", "()Landroid/app/Notification;") \ METHOD (setPriority, "setPriority", "(I)Landroid/app/Notification$Builder;") \ METHOD (setSubText, "setSubText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ METHOD (setUsesChronometer, "setUsesChronometer", "(Z)Landroid/app/Notification$Builder;") -DECLARE_JNI_CLASS (NotificationBuilderApi16, "android/app/Notification$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi16, "android/app/Notification$Builder", 16) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 17 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setShowWhen, "setShowWhen", "(Z)Landroid/app/Notification$Builder;") -DECLARE_JNI_CLASS (NotificationBuilderApi17, "android/app/Notification$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi17, "android/app/Notification$Builder", 17) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 20 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addAction, "addAction", "(Landroid/app/Notification$Action;)Landroid/app/Notification$Builder;") \ METHOD (addExtras, "addExtras", "(Landroid/os/Bundle;)Landroid/app/Notification$Builder;") \ METHOD (setLocalOnly, "setLocalOnly", "(Z)Landroid/app/Notification$Builder;") \ @@ -124,41 +113,34 @@ DECLARE_JNI_CLASS (NotificationBuilderApi17, "android/app/Notification$Builder") METHOD (setGroupSummary, "setGroupSummary", "(Z)Landroid/app/Notification$Builder;") \ METHOD (setSortKey, "setSortKey", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") -DECLARE_JNI_CLASS (NotificationBuilderApi20, "android/app/Notification$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi20, "android/app/Notification$Builder", 20) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 21 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addPerson, "addPerson", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \ METHOD (setCategory, "setCategory", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \ METHOD (setColor, "setColor", "(I)Landroid/app/Notification$Builder;") \ METHOD (setPublicVersion, "setPublicVersion", "(Landroid/app/Notification;)Landroid/app/Notification$Builder;") \ METHOD (setVisibility, "setVisibility", "(I)Landroid/app/Notification$Builder;") -DECLARE_JNI_CLASS (NotificationBuilderApi21, "android/app/Notification$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi21, "android/app/Notification$Builder", 21) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 24 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setChronometerCountDown, "setChronometerCountDown", "(Z)Landroid/app/Notification$Builder;") -DECLARE_JNI_CLASS (NotificationBuilderApi24, "android/app/Notification$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi24, "android/app/Notification$Builder", 24) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 26 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setBadgeIconType, "setBadgeIconType", "(I)Landroid/app/Notification$Builder;") \ METHOD (setGroupAlertBehavior, "setGroupAlertBehavior", "(I)Landroid/app/Notification$Builder;") \ METHOD (setTimeoutAfter, "setTimeoutAfter", "(J)Landroid/app/Notification$Builder;") -DECLARE_JNI_CLASS (NotificationBuilderApi26, "android/app/Notification$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi26, "android/app/Notification$Builder", 26) #undef JNI_CLASS_MEMBERS -#endif -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (cancel, "cancel", "(Ljava/lang/String;I)V") \ METHOD (cancelAll, "cancelAll", "()V") \ METHOD (notify, "notify", "(Ljava/lang/String;ILandroid/app/Notification;)V") @@ -166,56 +148,46 @@ DECLARE_JNI_CLASS (NotificationBuilderApi26, "android/app/Notification$Builder") DECLARE_JNI_CLASS (NotificationManagerBase, "android/app/NotificationManager") #undef JNI_CLASS_MEMBERS -#if __ANDROID_API__ >= 23 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getActiveNotifications, "getActiveNotifications", "()[Landroid/service/notification/StatusBarNotification;") -DECLARE_JNI_CLASS (NotificationManagerApi23, "android/app/NotificationManager") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi23, "android/app/NotificationManager", 23) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 24 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (areNotificationsEnabled, "areNotificationsEnabled", "()Z") -DECLARE_JNI_CLASS (NotificationManagerApi24, "android/app/NotificationManager") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi24, "android/app/NotificationManager", 24) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 26 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (createNotificationChannel, "createNotificationChannel", "(Landroid/app/NotificationChannel;)V") \ METHOD (createNotificationChannelGroup, "createNotificationChannelGroup", "(Landroid/app/NotificationChannelGroup;)V") -DECLARE_JNI_CLASS (NotificationManagerApi26, "android/app/NotificationManager") +DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi26, "android/app/NotificationManager", 26) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 20 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getResultsFromIntent, "getResultsFromIntent", "(Landroid/content/Intent;)Landroid/os/Bundle;") -DECLARE_JNI_CLASS (RemoteInput, "android/app/RemoteInput") +DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInput, "android/app/RemoteInput", 20) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Ljava/lang/String;)V") \ METHOD (build, "build", "()Landroid/app/RemoteInput;") \ METHOD (setAllowFreeFormInput, "setAllowFreeFormInput", "(Z)Landroid/app/RemoteInput$Builder;") \ METHOD (setChoices, "setChoices", "([Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;") \ METHOD (setLabel, "setLabel", "(Ljava/lang/CharSequence;)Landroid/app/RemoteInput$Builder;") -DECLARE_JNI_CLASS (RemoteInputBuilder, "android/app/RemoteInput$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInputBuilder, "android/app/RemoteInput$Builder", 20) #undef JNI_CLASS_MEMBERS -#endif -#if __ANDROID_API__ >= 23 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (getNotification, "getNotification", "()Landroid/app/Notification;") +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (getNotification, "getNotification", "()Landroid/app/Notification;") - DECLARE_JNI_CLASS (StatusBarNotification, "android/service/notification/StatusBarNotification") + DECLARE_JNI_CLASS_WITH_MIN_SDK (StatusBarNotification, "android/service/notification/StatusBarNotification", 23) #undef JNI_CLASS_MEMBERS -#endif //========================================================================== #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) @@ -284,10 +256,8 @@ DECLARE_JNI_CLASS (RemoteInputBuilder, "android/app/RemoteInput$Builder") //============================================================================== bool PushNotifications::Notification::isValid() const noexcept { - auto* env = getEnv(); - bool isValidForPreApi26 = title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && icon.isNotEmpty(); - bool apiAtLeast26 = env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion) >= 26; + bool apiAtLeast26 = (getAndroidSDKVersion() >= 26); if (apiAtLeast26) return isValidForPreApi26 && channelId.isNotEmpty(); @@ -304,18 +274,17 @@ struct PushNotifications::Pimpl bool areNotificationsEnabled() const { - #if __ANDROID_API__ >= 24 - auto* env = getEnv(); + if (getAndroidSDKVersion() >= 24) + { + auto* env = getEnv(); - auto notificationManager = getNotificationManager(); + auto notificationManager = getNotificationManager(); - if (notificationManager.get() != 0) - return env->CallBooleanMethod (notificationManager, NotificationManagerApi24.areNotificationsEnabled); + if (notificationManager.get() != 0) + return env->CallBooleanMethod (notificationManager, NotificationManagerApi24.areNotificationsEnabled); + } - return false; - #else return true; - #endif } //========================================================================== @@ -341,48 +310,52 @@ struct PushNotifications::Pimpl void getDeliveredNotifications() const { - #if __ANDROID_API__ >= 23 - auto* env = getEnv(); + if (getAndroidSDKVersion() >= 23) + { + auto* env = getEnv(); - Array notifications; + Array notifications; - auto notificationManager = getNotificationManager(); + auto notificationManager = getNotificationManager(); - jassert (notificationManager.get() != 0); + jassert (notificationManager.get() != 0); - if (notificationManager.get() != 0) - { - auto statusBarNotifications = LocalRef ((jobjectArray)env->CallObjectMethod (notificationManager, - NotificationManagerApi23.getActiveNotifications)); + if (notificationManager.get() != 0) + { + auto statusBarNotifications = LocalRef ((jobjectArray)env->CallObjectMethod (notificationManager, + NotificationManagerApi23.getActiveNotifications)); - const int numNotifications = env->GetArrayLength (statusBarNotifications.get()); + const int numNotifications = env->GetArrayLength (statusBarNotifications.get()); - for (int i = 0; i < numNotifications; ++i) - { - auto statusBarNotification = LocalRef (env->GetObjectArrayElement (statusBarNotifications.get(), (jsize) i)); - auto notification = LocalRef (env->CallObjectMethod (statusBarNotification, StatusBarNotification.getNotification)); + for (int i = 0; i < numNotifications; ++i) + { + auto statusBarNotification = LocalRef (env->GetObjectArrayElement (statusBarNotifications.get(), (jsize) i)); + auto notification = LocalRef (env->CallObjectMethod (statusBarNotification, StatusBarNotification.getNotification)); - notifications.add (javaNotificationToJuceNotification (notification)); + notifications.add (javaNotificationToJuceNotification (notification)); + } } - } - owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifications); }); - #else - // Not supported on this platform - jassertfalse; - owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); }); - #endif + owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifications); }); + } + else + { + // Not supported on this platform + jassertfalse; + owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); }); + } } void notifyListenersAboutLocalNotification (const LocalRef& intent) { auto* env = getEnv(); + LocalRef context (getAppContext()); auto bundle = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); const auto notification = localNotificationBundleToJuceNotification (bundle); - auto packageName = juceString ((jstring) (android.activity.callObjectMethod (JuceAppActivity.getPackageName))); + auto packageName = juceString ((jstring) env->CallObjectMethod (context.get(), AndroidContext.getPackageName)); String notificationString = packageName + ".JUCE_NOTIFICATION."; String notificationButtonActionString = packageName + ".JUCE_NOTIFICATION_BUTTON_ACTION."; @@ -403,8 +376,7 @@ struct PushNotifications::Pimpl owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, {}); }); } - #if __ANDROID_API__ >= 20 - else if (actionString.contains (notificationTextInputActionString)) + else if (getAndroidSDKVersion() >= 20 && actionString.contains (notificationTextInputActionString)) { auto prefix = notificationTextInputActionString + notification.identifier + "."; @@ -426,7 +398,6 @@ struct PushNotifications::Pimpl owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, responseString); }); } - #endif } void notifyListenersAboutLocalNotificationDeleted (const LocalRef& intent) @@ -632,9 +603,10 @@ struct PushNotifications::Pimpl static LocalRef getNotificationManager() { auto* env = getEnv(); + LocalRef context (getAppContext()); - return LocalRef (env->CallObjectMethod (android.activity, - JuceAppActivity.getSystemService, + return LocalRef (env->CallObjectMethod (context.get(), + AndroidContext.getSystemService, javaString ("notification").get())); } @@ -650,16 +622,16 @@ struct PushNotifications::Pimpl if (n.actions.size() > 0) setupActions (n, notificationBuilder); - #if __ANDROID_API__ >= 16 - return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build)); - #else + if (getAndroidSDKVersion() >= 16) + return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build)); + return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.getNotification)); - #endif } static LocalRef createNotificationBuilder (const PushNotifications::Notification& n) { auto* env = getEnv(); + LocalRef context (getAppContext()); jclass builderClass = env->FindClass ("android/app/Notification$Builder"); jassert (builderClass != 0); @@ -669,7 +641,7 @@ struct PushNotifications::Pimpl jmethodID builderConstructor = 0; - const bool apiAtLeast26 = env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion) >= 26; + const bool apiAtLeast26 = (getAndroidSDKVersion() >= 26); if (apiAtLeast26) builderConstructor = env->GetMethodID (builderClass, "", "(Landroid/content/Context;Ljava/lang/String;)V"); @@ -683,19 +655,20 @@ struct PushNotifications::Pimpl if (apiAtLeast26) return LocalRef (env->NewObject (builderClass, builderConstructor, - android.activity.get(), javaString (n.channelId).get())); + context.get(), javaString (n.channelId).get())); - return LocalRef (env->NewObject (builderClass, builderConstructor, android.activity.get())); + return LocalRef (env->NewObject (builderClass, builderConstructor, context.get())); } static void setupRequiredFields (const PushNotifications::Notification& n, LocalRef& notificationBuilder) { auto* env = getEnv(); + LocalRef context (getAppContext()); - auto activityClass = LocalRef (env->CallObjectMethod (android.activity, JavaObject.getClass)); - auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, android.activity.get(), activityClass.get())); + auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); + auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); - auto packageNameString = LocalRef ((jstring) (android.activity.callObjectMethod (JuceAppActivity.getPackageName))); + auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); auto actionStringSuffix = javaString (".JUCE_NOTIFICATION." + n.identifier); auto actionString = LocalRef ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get())); @@ -705,7 +678,7 @@ struct PushNotifications::Pimpl auto notifyPendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, AndroidPendingIntent.getActivity, - android.activity.get(), + context.get(), 1002, notifyIntent.get(), 0)); @@ -714,14 +687,13 @@ struct PushNotifications::Pimpl env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentText, javaString (n.body).get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentIntent, notifyPendingIntent.get()); - auto resources = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getResources)); + auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); const int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), javaString ("raw").get(), packageNameString.get()); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSmallIcon, iconId); - #if __ANDROID_API__ >= 21 - if (n.publicVersion != nullptr) + if (getAndroidSDKVersion() >= 21 && n.publicVersion != nullptr) { // Public version of a notification is not expected to have another public one! jassert (n.publicVersion->publicVersion == nullptr); @@ -734,7 +706,6 @@ struct PushNotifications::Pimpl auto publicVersion = LocalRef (env->CallObjectMethod (publicNotificationBuilder, NotificationBuilderApi16.build)); env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setPublicVersion, publicVersion.get()); } - #endif } static LocalRef juceNotificationToBundle (const PushNotifications::Notification& n) @@ -852,70 +823,77 @@ struct PushNotifications::Pimpl env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOngoing, n.ongoing); env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOnlyAlertOnce, n.alertOnlyOnce); - #if __ANDROID_API__ >= 16 - if (n.subtitle.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setSubText, javaString (n.subtitle).get()); - - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setPriority, n.priority); + if (getAndroidSDKVersion() >= 16) + { + if (n.subtitle.isNotEmpty()) + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setSubText, javaString (n.subtitle).get()); - #if __ANDROID_API__ < 24 - const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer); - #endif - #endif + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setPriority, n.priority); - #if __ANDROID_API__ >= 17 - const bool showTimeStamp = n.timestampVisibility != PushNotifications::Notification::off; - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi17.setShowWhen, showTimeStamp); - #endif + if (getAndroidSDKVersion() < 24) + { + const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer); + } + } - #if __ANDROID_API__ >= 20 - if (n.groupId.isNotEmpty()) + if (getAndroidSDKVersion() >= 17) { - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroup, javaString (n.groupId).get()); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroupSummary, n.groupSummary); + const bool showTimeStamp = n.timestampVisibility != PushNotifications::Notification::off; + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi17.setShowWhen, showTimeStamp); } - if (n.groupSortKey.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setSortKey, javaString (n.groupSortKey).get()); + if (getAndroidSDKVersion() >= 20) + { + if (n.groupId.isNotEmpty()) + { + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroup, javaString (n.groupId).get()); + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroupSummary, n.groupSummary); + } - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setLocalOnly, n.localOnly); + if (n.groupSortKey.isNotEmpty()) + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setSortKey, javaString (n.groupSortKey).get()); - auto extras = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setLocalOnly, n.localOnly); - env->CallVoidMethod (extras, AndroidBundle.putBundle, javaString ("notificationData").get(), - juceNotificationToBundle (n).get()); + auto extras = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addExtras, extras.get()); - #endif + env->CallVoidMethod (extras, AndroidBundle.putBundle, javaString ("notificationData").get(), + juceNotificationToBundle (n).get()); - #if __ANDROID_API__ >= 21 - if (n.person.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.addPerson, javaString (n.person).get()); + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addExtras, extras.get()); + } - auto categoryString = typeToCategory (n.type); - if (categoryString.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setCategory, javaString (categoryString).get()); + if (getAndroidSDKVersion() >= 21) + { + if (n.person.isNotEmpty()) + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.addPerson, javaString (n.person).get()); - if (n.accentColour != Colour()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setColor, n.accentColour.getARGB()); + auto categoryString = typeToCategory (n.type); + if (categoryString.isNotEmpty()) + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setCategory, javaString (categoryString).get()); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setVisibility, n.lockScreenAppearance); - #endif + if (n.accentColour != Colour()) + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setColor, n.accentColour.getARGB()); - #if __ANDROID_API__ >= 24 - const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; - const bool useCountDownChronometer = n.timestampVisibility == PushNotifications::Notification::countDownChronometer; + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setVisibility, n.lockScreenAppearance); + } - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi24.setChronometerCountDown, useCountDownChronometer); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer | useCountDownChronometer); - #endif + if (getAndroidSDKVersion() >= 24) + { + const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; + const bool useCountDownChronometer = n.timestampVisibility == PushNotifications::Notification::countDownChronometer; - #if __ANDROID_API__ >= 26 - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setBadgeIconType, n.badgeIconType); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setGroupAlertBehavior, n.groupAlertBehaviour); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setTimeoutAfter, (jlong) n.timeoutAfterMs); - #endif + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi24.setChronometerCountDown, useCountDownChronometer); + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer | useCountDownChronometer); + } + + if (getAndroidSDKVersion() >= 26) + { + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setBadgeIconType, n.badgeIconType); + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setGroupAlertBehavior, n.groupAlertBehaviour); + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi26.setTimeoutAfter, (jlong) n.timeoutAfterMs); + } setupNotificationDeletedCallback (n, notificationBuilder); } @@ -924,11 +902,12 @@ struct PushNotifications::Pimpl LocalRef& notificationBuilder) { auto* env = getEnv(); + LocalRef context (getAppContext()); - auto activityClass = LocalRef (env->CallObjectMethod (android.activity, JavaObject.getClass)); - auto deleteIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, android.activity.get(), activityClass.get())); + auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); + auto deleteIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); - auto packageNameString = LocalRef ((jstring) (android.activity.callObjectMethod (JuceAppActivity.getPackageName))); + auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); auto actionStringSuffix = javaString (".JUCE_NOTIFICATION_DELETED." + n.identifier); auto actionString = LocalRef ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get())); @@ -937,7 +916,7 @@ struct PushNotifications::Pimpl auto deletePendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, AndroidPendingIntent.getActivity, - android.activity.get(), + context.get(), 1002, deleteIntent.get(), 0)); @@ -947,19 +926,22 @@ struct PushNotifications::Pimpl static void setupActions (const PushNotifications::Notification& n, LocalRef& notificationBuilder) { - #if __ANDROID_API__ >= 16 + if (getAndroidSDKVersion() < 16) + return; + auto* env = getEnv(); + LocalRef context (getAppContext()); int actionIndex = 0; for (const auto& action : n.actions) { - auto activityClass = LocalRef (env->CallObjectMethod (android.activity, JavaObject.getClass)); - auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, android.activity.get(), activityClass.get())); + auto activityClass = LocalRef (env->CallObjectMethod (context.get(), JavaObject.getClass)); + auto notifyIntent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), activityClass.get())); const bool isTextStyle = action.style == PushNotifications::Notification::Action::text; - auto packageNameString = LocalRef ((jstring) (android.activity.callObjectMethod (JuceAppActivity.getPackageName))); + auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); const String notificationActionString = isTextStyle ? ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." : ".JUCE_NOTIFICATION_BUTTON_ACTION."; auto actionStringSuffix = javaString (notificationActionString + n.identifier + "." + String (actionIndex) + "." + action.title); auto actionString = LocalRef ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get())); @@ -970,12 +952,12 @@ struct PushNotifications::Pimpl auto notifyPendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, AndroidPendingIntent.getActivity, - android.activity.get(), + context.get(), 1002, notifyIntent.get(), 0)); - auto resources = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getResources)); + auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); int iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (action.icon).get(), javaString ("raw").get(), packageNameString.get()); @@ -983,70 +965,71 @@ struct PushNotifications::Pimpl iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), javaString ("raw").get(), packageNameString.get()); - #if __ANDROID_API__ >= 20 - auto actionBuilder = LocalRef (env->NewObject (NotificationActionBuilder, - NotificationActionBuilder.constructor, - iconId, - javaString (action.title).get(), - notifyPendingIntent.get())); - - env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addExtras, - varToBundleWithPropertiesString (action.parameters).get()); - - if (isTextStyle) + if (getAndroidSDKVersion() >= 20) { - auto resultKey = javaString (action.title + String (actionIndex)); - auto remoteInputBuilder = LocalRef (env->NewObject (RemoteInputBuilder, - RemoteInputBuilder.constructor, - resultKey.get())); + auto actionBuilder = LocalRef (env->NewObject (NotificationActionBuilder, + NotificationActionBuilder.constructor, + iconId, + javaString (action.title).get(), + notifyPendingIntent.get())); - if (! action.textInputPlaceholder.isEmpty()) - env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setLabel, javaString (action.textInputPlaceholder).get()); + env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addExtras, + varToBundleWithPropertiesString (action.parameters).get()); - if (! action.allowedResponses.isEmpty()) + if (isTextStyle) { - env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setAllowFreeFormInput, false); + auto resultKey = javaString (action.title + String (actionIndex)); + auto remoteInputBuilder = LocalRef (env->NewObject (RemoteInputBuilder, + RemoteInputBuilder.constructor, + resultKey.get())); - const int size = action.allowedResponses.size(); + if (! action.textInputPlaceholder.isEmpty()) + env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setLabel, javaString (action.textInputPlaceholder).get()); - auto array = LocalRef (env->NewObjectArray (size, env->FindClass ("java/lang/String"), 0)); - - for (int i = 0; i < size; ++i) + if (! action.allowedResponses.isEmpty()) { - const auto& response = action.allowedResponses[i]; - auto responseString = javaString (response); + env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setAllowFreeFormInput, false); + + const int size = action.allowedResponses.size(); + + auto array = LocalRef (env->NewObjectArray (size, env->FindClass ("java/lang/String"), 0)); - env->SetObjectArrayElement (array, i, responseString.get()); + for (int i = 0; i < size; ++i) + { + const auto& response = action.allowedResponses[i]; + auto responseString = javaString (response); + + env->SetObjectArrayElement (array, i, responseString.get()); + } + + env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setChoices, array.get()); } - env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setChoices, array.get()); + env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addRemoteInput, + env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.build)); } - env->CallObjectMethod (actionBuilder, NotificationActionBuilder.addRemoteInput, - env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.build)); + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addAction, + env->CallObjectMethod (actionBuilder, NotificationActionBuilder.build)); + } + else + { + env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.addAction, + iconId, javaString (action.title).get(), notifyPendingIntent.get()); } - - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addAction, - env->CallObjectMethod (actionBuilder, NotificationActionBuilder.build)); - #else - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.addAction, - iconId, javaString (action.title).get(), notifyPendingIntent.get()); - #endif ++actionIndex; } - #else - ignoreUnused (n, notificationBuilder); - #endif // #if __ANDROID_API__ >= 16 } static LocalRef juceUrlToAndroidUri (const URL& url) { auto* env = getEnv(); + LocalRef context (getAppContext()); - auto packageNameString = LocalRef ((jstring) (android.activity.callObjectMethod (JuceAppActivity.getPackageName))); + auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); - auto resources = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getResources)); + auto resources = LocalRef (env->CallObjectMethod (context.get(), AndroidContext.getResources)); const int id = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (url.toString (true)).get(), javaString ("raw").get(), packageNameString.get()); @@ -1270,7 +1253,9 @@ struct PushNotifications::Pimpl static PushNotifications::Notification javaNotificationToJuceNotification (const LocalRef& notification) { - #if __ANDROID_API__ >= 20 + if (getAndroidSDKVersion() < 20) + return {}; + auto* env = getEnv(); auto extras = LocalRef (env->GetObjectField (notification, AndroidNotification.extras)); @@ -1279,12 +1264,8 @@ struct PushNotifications::Pimpl if (notificationData.get() != nullptr) return localNotificationBundleToJuceNotification (notificationData); - else - return remoteNotificationBundleToJuceNotification (extras); - #else - ignoreUnused (notification); - return {}; - #endif + + return remoteNotificationBundleToJuceNotification (extras); } static PushNotifications::Notification remoteNotificationBundleToJuceNotification (const LocalRef& bundle) @@ -1429,12 +1410,11 @@ struct PushNotifications::Pimpl void setupChannels (const Array& groups, const Array& channels) { - #if __ANDROID_API__ >= 26 - auto* env = getEnv(); - - if (env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion) < 26) + if (getAndroidSDKVersion() < 26) return; + auto* env = getEnv(); + auto notificationManager = getNotificationManager(); jassert (notificationManager.get() != 0); @@ -1503,9 +1483,6 @@ struct PushNotifications::Pimpl env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get()); } - #else - ignoreUnused (groups, channels); - #endif } void getPendingLocalNotifications() const {} @@ -1515,9 +1492,10 @@ struct PushNotifications::Pimpl static bool intentActionContainsAnyOf (jobject intent, const StringArray& strings, bool includePackageName) { auto* env = getEnv(); + LocalRef context (getAppContext()); - String packageName = includePackageName ? juceString ((jstring) env->CallObjectMethod (android.activity, - JuceAppActivity.getPackageName)) + String packageName = includePackageName ? juceString ((jstring) env->CallObjectMethod (context.get(), + AndroidContext.getPackageName)) : String{}; String intentAction = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction)); diff --git a/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp index 8e79b41cc7..7c45b05f05 100644 --- a/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp @@ -27,7 +27,122 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +//============================================================================== +// This byte-code is generated from native/java/com/roli/juce/JuceWebView.java with min sdk version 16 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const unsigned char JuceWebView16ByteCode[] = +{31,139,8,8,167,106,229,91,0,3,74,117,99,101,87,101,98,86,105,101,119,49,54,46,100,101,120,0,125,150,93,108,84,69,20,199, +207,253,216,189,187,183,237,178,109,161,31,20,74,91,177,84,164,44,88,145,210,5,172,124,83,22,63,186,80,100,53,145,219, +221,177,189,101,123,239,114,239,221,182,38,68,81,155,168,4,19,99,225,141,23,19,36,38,248,136,9,15,62,16,35,209,24,141,47, +70,125,224,193,23,141,15,62,136,49,132,104,31,252,207,157,217,101,129,98,55,191,61,103,206,57,51,115,102,230,116,118,10, +108,206,220,52,176,133,222,95,60,244,253,183,71,91,94,92,240,223,75,222,186,115,227,231,111,110,221,252,44,186,176,245, +181,235,117,68,37,34,154,27,123,178,137,228,223,162,73,52,76,194,190,156,75,133,168,1,242,58,164,14,249,178,74,212,12, +121,26,82,227,49,248,202,196,136,2,200,250,40,218,160,7,172,7,253,224,105,176,7,188,2,78,131,15,193,85,240,35,88,4,45,6, +209,83,224,4,152,7,31,129,27,224,87,16,195,184,107,192,0,216,15,70,192,179,32,11,142,131,19,160,0,108,224,2,15,204,129, +55,192,57,176,0,46,130,143,193,21,112,13,124,1,190,3,63,129,223,192,31,224,95,80,23,39,234,0,235,193,78,112,16,88,96,26, +204,129,215,193,219,224,44,248,0,92,1,95,129,95,192,29,208,104,138,253,192,146,8,169,19,134,36,152,9,102,194,54,83,61, +137,125,76,128,101,32,9,26,1,223,248,102,185,215,43,64,11,104,5,107,64,68,142,119,41,34,108,149,67,106,147,250,167,176, +183,75,253,42,244,14,169,127,14,125,165,212,191,134,190,74,234,63,64,239,148,250,77,232,171,165,126,169,198,254,123,141, +254,55,244,46,153,31,31,167,91,234,60,41,190,182,222,112,141,73,234,147,235,236,13,165,104,71,72,33,17,106,134,50,38,219, +113,82,165,140,82,127,40,235,105,99,40,53,26,146,50,29,142,35,226,76,244,91,23,202,24,165,66,25,167,77,161,52,104,179, +156,119,32,148,17,218,30,202,58,218,17,74,157,118,134,123,47,230,77,86,231,167,80,139,200,189,228,53,29,160,113,89,164, +25,142,167,200,243,171,248,231,225,255,82,250,235,164,63,89,227,63,15,255,95,210,207,179,158,135,126,214,188,171,47,152, +162,207,69,147,199,107,161,222,110,138,122,40,37,185,175,7,227,149,146,124,207,95,74,42,148,107,18,117,162,99,4,62,126, +175,41,234,32,139,195,40,13,71,73,221,156,192,234,35,161,175,223,20,245,38,124,6,124,77,97,125,85,230,217,90,157,71,189, +111,30,13,243,168,225,60,226,172,20,218,99,138,58,61,242,140,70,171,149,22,164,159,219,165,82,167,146,192,8,157,202,58, +89,143,10,62,113,204,169,133,237,195,166,168,231,236,176,74,188,7,206,68,221,6,95,34,180,148,198,18,164,191,208,247,15, +223,79,61,140,31,51,197,218,106,227,7,49,154,136,94,134,232,4,246,88,15,215,123,194,20,245,150,45,61,48,182,167,146,113, +202,152,55,46,24,151,103,162,252,108,251,110,243,179,225,57,169,52,133,126,143,240,26,86,142,188,133,149,168,217,121,244, +199,128,155,53,61,58,168,213,19,111,151,114,203,232,192,5,147,6,49,87,167,218,168,116,170,61,106,148,86,106,91,176,139, +10,61,209,104,116,247,221,110,128,117,157,188,247,154,49,71,119,184,75,252,211,37,37,238,26,83,248,197,174,38,194,123, +176,246,239,204,125,237,115,247,181,121,141,24,184,9,148,154,54,183,232,85,169,146,38,245,70,89,123,252,188,181,170,183, +162,139,49,184,222,136,207,50,89,155,6,50,111,134,53,186,221,118,236,96,39,213,239,158,244,220,105,182,187,104,51,39,160, +168,148,202,8,37,71,202,121,118,140,141,143,217,108,118,227,148,53,99,145,150,201,100,168,61,99,57,5,207,181,11,169,9, +207,42,77,218,121,63,181,203,14,166,173,82,154,58,170,46,135,5,169,201,32,40,165,178,126,113,175,231,185,94,154,150,87, +157,174,159,58,204,124,223,154,96,105,234,170,90,103,217,248,73,59,168,118,56,0,123,145,121,75,68,32,165,218,148,211,180, +118,137,136,81,230,187,101,47,207,32,75,174,227,99,166,182,37,162,248,210,210,212,249,16,79,101,252,190,76,222,157,78, +121,110,209,78,77,97,75,82,53,251,178,246,222,76,122,254,47,82,198,116,60,60,134,15,80,176,138,51,246,201,148,229,56,110, +96,5,182,235,164,246,58,249,162,235,219,206,196,238,162,229,251,60,221,7,99,14,58,14,243,164,191,123,9,255,97,54,61,46,3, +24,66,86,100,248,121,166,108,23,29,75,229,32,27,120,204,154,78,83,147,48,23,45,103,34,245,220,248,20,203,7,247,218,16, +135,52,210,164,140,145,58,54,66,218,216,72,134,116,124,101,40,194,191,51,176,102,96,205,112,43,111,42,57,210,115,161,59, +151,201,229,50,84,103,229,243,56,248,125,69,107,194,167,8,227,199,76,198,171,214,140,157,119,29,50,38,197,137,147,62,233, +250,1,213,241,239,61,172,200,2,86,160,24,111,100,220,252,73,138,115,237,136,123,212,103,20,179,253,61,182,85,116,39,168, +193,246,97,240,246,51,63,40,123,140,116,199,154,102,212,224,58,187,177,111,236,152,237,20,220,89,74,160,137,85,6,53,237, +231,81,129,251,240,79,224,79,98,138,6,209,206,6,150,199,103,108,114,157,81,150,103,246,12,43,84,42,146,226,30,243,203, +197,224,176,63,65,45,254,164,91,46,22,14,58,1,67,145,149,130,81,118,170,140,217,201,20,246,140,107,21,40,30,176,57,254, +95,48,93,36,61,152,180,125,210,202,94,145,34,51,86,177,140,28,103,112,222,212,62,91,169,180,106,162,149,145,86,86,92,53, +73,87,124,173,210,199,19,230,83,85,23,209,114,159,163,178,154,74,135,7,150,20,157,21,187,49,170,172,55,18,106,107,90,157, +154,237,167,227,202,176,145,200,209,9,125,232,241,13,3,92,107,14,189,219,210,234,1,120,219,201,72,236,56,212,185,138,186, +213,161,65,35,113,118,21,110,209,161,193,71,141,196,59,57,122,76,27,234,93,27,218,182,114,231,234,29,239,78,105,180,101, +121,127,119,132,58,214,44,224,50,52,18,164,214,43,131,109,117,106,131,218,163,199,55,180,42,21,69,85,19,202,224,42,181, +45,222,134,31,122,77,37,85,105,210,222,60,163,159,55,180,183,240,59,5,116,229,186,161,40,55,241,131,166,71,84,120,99,240, +46,26,81,233,229,196,149,79,98,136,0,231,226,138,114,13,252,25,231,247,99,35,34,111,154,149,223,103,165,70,14,147,120, +215,242,59,179,242,182,229,247,101,237,251,182,242,198,141,208,221,119,110,148,238,190,117,181,164,208,249,61,175,116, +137,247,194,121,232,209,46,105,71,71,37,41,236,252,93,165,118,137,121,249,219,88,147,241,252,183,95,239,146,111,11,110, +144,125,195,55,72,82,228,202,223,225,255,1,121,75,63,47,192,11,0,0}; + +//============================================================================== +// This byte-code is generated from native/javacore/app/com/roli/juce/JuceWebView21.java with min sdk version 21 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const unsigned char JuceWebView21ByteCode[] = +{31,139,8,8,20,250,226,91,0,3,74,117,99,101,87,101,98,86,105,101,119,50,49,46,100,101,120,0,149,151,93,108,28,87,21,199, +207,124,236,206,174,119,118,179,222,117,28,219,113,236,181,235,54,78,147,116,227,36,37,78,54,169,204,58,113,237,48,81, +193,78,220,104,145,144,198,187,131,61,206,122,102,51,51,187,142,196,3,161,141,0,209,10,169,130,190,81,41,15,41,170,10,21, +66,66,34,60,240,134,74,16,47,149,120,160,128,84,1,2,169,15,17,42,15,84,81,37,36,254,247,99,54,27,199,68,176,171,223,158, +115,207,57,115,239,61,247,158,153,189,211,112,110,244,29,59,241,60,93,89,253,237,71,111,149,22,172,113,253,199,127,126, +238,195,175,252,238,212,236,173,175,189,250,151,87,111,127,146,37,106,17,209,141,213,147,5,146,159,35,176,45,145,176,15, +130,239,43,68,204,121,31,82,135,252,149,74,52,4,249,55,72,13,242,14,126,238,167,225,131,179,101,160,47,240,77,240,93,240, +6,120,27,188,3,222,3,247,192,159,192,3,144,75,17,29,3,203,96,27,188,5,126,14,126,15,52,244,119,8,44,130,38,120,29,252,4, +188,15,254,8,254,1,254,9,254,5,62,3,212,71,100,128,12,200,131,65,48,12,198,193,51,224,4,56,3,150,192,50,168,129,58,112, +65,7,220,4,175,129,55,193,29,240,46,248,5,248,13,248,3,248,24,252,27,244,103,136,70,192,211,224,28,88,2,151,65,13,52,128, +11,90,160,3,222,0,239,129,247,193,71,224,1,232,55,197,154,33,125,66,154,36,167,78,112,17,92,132,165,167,28,216,3,242,160, +159,196,218,23,193,0,216,43,247,100,31,137,61,24,6,35,96,18,36,128,42,247,48,41,251,255,56,249,80,255,36,41,98,138,50, +102,68,246,201,62,251,165,254,0,49,163,177,29,147,28,147,122,170,71,31,128,126,64,234,37,232,227,82,63,2,125,66,234,39, +123,244,57,232,37,169,179,57,196,118,171,39,230,42,244,167,100,126,172,207,41,169,55,12,177,54,135,249,26,21,232,168,92, +167,195,92,138,182,138,21,125,129,231,172,147,156,54,29,226,57,239,225,237,62,105,207,240,236,153,236,163,227,92,246,211, +9,46,147,84,149,114,158,247,43,226,76,92,119,132,203,44,157,228,50,71,207,115,105,210,231,184,204,208,41,46,21,58,195, +165,42,101,154,206,115,153,167,11,92,166,104,129,75,131,94,228,123,44,230,83,232,206,139,208,131,216,39,246,97,214,187, +104,252,218,36,57,15,225,239,235,241,223,131,255,239,210,159,149,254,66,143,255,67,248,199,179,162,205,106,226,109,196, +254,200,124,168,255,204,20,215,252,210,100,241,26,215,135,77,209,87,43,175,160,61,137,254,90,121,86,103,95,70,187,86,16, +117,169,163,7,214,255,51,166,152,239,10,54,182,53,151,34,117,38,135,236,18,220,119,212,20,123,32,124,105,248,10,124,39, +226,113,78,117,199,209,119,140,163,97,28,149,143,147,224,145,10,45,152,34,255,203,159,215,232,128,50,136,233,215,170,42, +141,41,57,244,48,166,28,228,59,149,36,54,223,52,198,212,120,251,37,83,220,63,43,115,42,177,43,102,144,246,105,248,114, +220,210,90,197,186,127,105,250,51,86,31,58,143,191,106,138,220,122,227,103,209,155,136,46,34,58,135,61,210,121,190,107, +166,184,127,86,90,143,245,29,168,100,92,55,110,25,111,26,63,236,36,7,48,163,233,79,169,123,93,243,127,188,110,111,247,58, +150,139,74,29,83,212,122,65,185,252,10,86,64,93,185,133,235,209,225,140,166,39,103,181,1,98,237,32,175,162,54,76,205,203, +179,103,139,169,181,86,138,180,136,7,217,44,198,30,83,251,149,49,117,82,77,209,136,118,22,187,161,209,241,126,99,98,250, +211,44,172,7,229,243,124,63,198,60,196,87,155,125,167,187,99,191,102,138,123,114,247,177,77,62,118,171,246,255,141,85, +196,8,19,221,177,74,82,18,221,54,133,95,84,66,142,255,151,244,126,242,59,218,163,59,218,172,174,89,5,8,89,228,125,198, +118,102,209,165,76,32,183,88,55,80,1,154,212,139,242,30,98,207,99,173,27,25,235,6,127,126,106,61,125,107,60,151,34,127, +62,235,220,190,23,95,236,245,89,215,115,163,23,200,156,223,8,252,45,103,190,233,58,94,68,73,41,149,139,84,184,216,174,59, +47,59,107,171,174,179,125,124,230,185,77,187,99,147,98,145,102,89,22,13,91,182,215,8,124,183,81,94,15,236,214,134,91,15, +203,85,55,218,178,91,21,234,239,186,60,39,42,95,9,220,10,237,127,196,180,17,69,173,242,74,216,188,16,4,126,80,161,129, +174,211,15,203,151,156,48,180,215,157,10,149,186,214,109,103,237,154,27,117,47,88,132,189,233,4,187,68,96,170,189,169,84, +232,169,93,34,150,157,208,111,7,117,103,217,185,222,118,66,4,77,61,49,40,108,249,94,136,233,12,237,18,197,214,165,66,99, +255,197,19,79,226,89,171,238,111,149,3,191,233,150,55,177,158,229,71,22,117,234,209,9,79,61,57,86,70,29,120,82,84,133,38, +173,134,221,236,184,215,202,182,231,249,145,29,185,190,87,190,224,213,155,126,232,122,235,243,77,59,12,217,164,31,143,89, +242,60,39,144,254,137,93,252,151,156,173,53,25,224,32,100,175,197,10,162,236,250,184,176,213,142,86,162,192,177,183,42, +84,16,230,166,237,173,151,95,90,219,116,234,209,163,54,196,97,26,21,82,86,73,93,189,72,218,234,69,139,116,252,88,148,96, +191,22,172,40,177,85,139,89,89,83,169,145,94,227,238,154,85,171,89,148,177,235,117,212,200,66,211,94,15,41,225,176,138, +160,44,23,241,102,145,241,85,187,227,214,125,143,146,235,78,116,37,104,146,177,33,106,134,244,13,63,140,40,195,126,207, +59,77,39,114,26,148,98,13,203,175,95,163,52,211,46,251,87,208,67,202,13,207,187,118,211,95,167,172,27,194,16,188,136,82, +105,7,14,233,158,189,229,80,214,247,230,177,156,206,203,174,215,240,183,41,135,38,146,143,122,218,95,68,13,47,224,246,10, +55,48,68,86,180,87,34,59,96,35,22,125,111,217,169,59,110,199,105,44,226,78,224,69,77,133,135,198,184,208,201,8,68,141,82, +58,112,194,118,51,186,20,174,211,96,184,225,183,155,141,37,47,114,80,159,173,72,150,49,245,9,187,229,219,13,74,71,206,13, +118,151,109,53,73,143,54,220,144,82,145,47,150,157,180,54,150,35,209,177,155,109,228,210,65,193,208,240,118,92,174,221, +132,226,62,71,98,87,79,114,177,111,159,244,177,196,216,160,221,100,7,119,56,226,172,135,164,253,241,212,247,237,240,116, +243,79,110,139,245,220,84,202,70,78,29,172,168,215,182,143,210,117,101,201,200,213,232,134,94,61,54,115,134,105,3,220, +251,1,85,212,159,126,29,254,17,50,114,231,190,48,54,74,147,106,117,206,200,125,103,148,142,107,213,185,67,70,238,91,53, +186,170,85,79,79,115,219,179,90,245,240,65,174,45,170,213,211,198,129,115,223,254,171,70,103,7,143,78,36,104,255,248,247, +104,150,93,11,227,102,175,113,15,122,38,53,167,204,13,101,213,61,234,211,122,122,102,159,18,43,170,154,87,230,70,213,161, +204,16,78,86,154,74,170,82,72,124,227,166,126,39,165,189,162,146,2,146,202,7,41,69,185,143,211,71,194,80,225,237,131,247, +245,116,74,122,99,50,202,221,52,162,192,15,250,20,229,30,184,153,81,148,187,64,28,130,6,113,213,109,118,30,201,203,255, +12,165,71,198,239,65,236,127,36,126,23,98,207,254,222,247,161,248,157,136,157,37,226,247,162,36,61,124,55,210,242,66,103, +255,105,74,73,156,103,166,160,39,75,194,206,206,122,74,94,156,137,216,121,93,45,201,113,113,136,210,100,60,59,155,233,37, +49,22,59,191,145,188,150,159,17,243,98,174,236,189,237,63,5,143,187,196,240,13,0,0}; + + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Landroid/content/Context;)V") \ METHOD (getSettings, "getSettings", "()Landroid/webkit/WebSettings;") \ METHOD (goBack, "goBack", "()V") \ @@ -43,38 +158,25 @@ namespace juce DECLARE_JNI_CLASS (AndroidWebView, "android/webkit/WebView") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") DECLARE_JNI_CLASS (AndroidWebChromeClient, "android/webkit/WebChromeClient") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") DECLARE_JNI_CLASS (AndroidWebViewClient, "android/webkit/WebViewClient") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getInstance, "getInstance", "()Landroid/webkit/CookieManager;") DECLARE_JNI_CLASS (AndroidCookieManager, "android/webkit/CookieManager") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") - -DECLARE_JNI_CLASS (JuceWebChromeClient, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceWebChromeClient") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") \ - METHOD (hostDeleted, "hostDeleted", "()V") - -DECLARE_JNI_CLASS (JuceWebViewClient, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceWebViewClient") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setBuiltInZoomControls, "setBuiltInZoomControls", "(Z)V") \ METHOD (setDisplayZoomControls, "setDisplayZoomControls", "(Z)V") \ METHOD (setJavaScriptEnabled, "setJavaScriptEnabled", "(Z)V") \ @@ -83,13 +185,13 @@ DECLARE_JNI_CLASS (JuceWebViewClient, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceWebV DECLARE_JNI_CLASS (WebSettings, "android/webkit/WebSettings") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (toString, "toString", "()Ljava/lang/String;") DECLARE_JNI_CLASS (SslError, "android/net/http/SslError") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (encode, "encode", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") DECLARE_JNI_CLASS (URLEncoder, "java/net/URLEncoder") @@ -101,12 +203,11 @@ class WebBrowserComponent::Pimpl : public AndroidViewComponent, { public: Pimpl (WebBrowserComponent& o) - : AndroidViewComponent (true), - owner (o) + : owner (o) { auto* env = getEnv(); - setView (env->NewObject (AndroidWebView, AndroidWebView.constructor, android.activity.get())); + setView (env->NewObject (AndroidWebView, AndroidWebView.constructor, getMainActivity().get())); auto settings = LocalRef (env->CallObjectMethod ((jobject) getView(), AndroidWebView.getSettings)); env->CallVoidMethod (settings, WebSettings.setJavaScriptEnabled, true); @@ -115,13 +216,18 @@ public: env->CallVoidMethod (settings, WebSettings.setSupportMultipleWindows, true); juceWebChromeClient = GlobalRef (LocalRef (env->NewObject (JuceWebChromeClient, JuceWebChromeClient.constructor, - android.activity.get(), - reinterpret_cast(&owner)))); + reinterpret_cast (this)))); env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get()); - juceWebViewClient = GlobalRef (LocalRef (env->NewObject (JuceWebViewClient, JuceWebViewClient.constructor, - android.activity.get(), - reinterpret_cast(&owner)))); + auto sdkVersion = getAndroidSDKVersion(); + + if (sdkVersion >= 21) + juceWebViewClient = GlobalRef (LocalRef (env->NewObject (JuceWebViewClient21, JuceWebViewClient21.constructor, + reinterpret_cast (this)))); + else + juceWebViewClient = GlobalRef (LocalRef (env->NewObject (JuceWebViewClient16, JuceWebViewClient16.constructor, + reinterpret_cast (this)))); + env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebViewClient, juceWebViewClient.get()); } @@ -144,7 +250,8 @@ public: // the lock we need when calling hostDeleted. responseReadyEvent.signal(); - env->CallVoidMethod (juceWebViewClient, JuceWebViewClient.hostDeleted); + env->CallVoidMethod (juceWebViewClient, getAndroidSDKVersion() >= 21 ? JuceWebViewClient21.hostDeleted + : JuceWebViewClient16.hostDeleted); } void goToURL (const String& url, @@ -281,6 +388,8 @@ public: return shouldLoad; } + WebBrowserComponent& owner; + private: class ConnectionThread : private Thread { @@ -361,8 +470,107 @@ private: Result result; }; + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + METHOD (hostDeleted, "hostDeleted", "()V") \ + CALLBACK (webViewReceivedHttpError, "webViewReceivedHttpError", "(JLandroid/webkit/WebView;Landroid/webkit/WebResourceRequest;Landroid/webkit/WebResourceResponse;)V") \ + CALLBACK (webViewPageLoadStarted, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \ + CALLBACK (webViewPageLoadFinished, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \ + CALLBACK (webViewReceivedSslError, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \ + + DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebViewClient21, "com/roli/juce/JuceWebView21$Client", 21, JuceWebView21ByteCode, sizeof (JuceWebView21ByteCode)) + #undef JNI_CLASS_MEMBERS + + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + METHOD (hostDeleted, "hostDeleted", "()V") \ + CALLBACK (webViewPageLoadStarted, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \ + CALLBACK (webViewPageLoadFinished, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \ + CALLBACK (webViewReceivedSslError, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \ + + DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebViewClient16, "com/roli/juce/JuceWebView$Client", 16, JuceWebView16ByteCode, sizeof (JuceWebView16ByteCode)) + #undef JNI_CLASS_MEMBERS + + static jboolean JNICALL webViewPageLoadStarted (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jstring url) + { + if (auto* myself = reinterpret_cast (host)) + return myself->handlePageAboutToLoad (juceString (url)); - WebBrowserComponent& owner; + return 0; + } + + static void JNICALL webViewPageLoadFinished (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jstring url) + { + if (auto* myself = reinterpret_cast (host)) + myself->owner.pageFinishedLoading (juceString (url)); + } + + static void JNICALL webViewReceivedHttpError (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject errorResponse) + { + if (auto* myself = reinterpret_cast (host)) + myself->webReceivedHttpError (errorResponse); + } + + static void JNICALL webViewReceivedSslError (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError) + { + auto* env = getEnv(); + + if (auto* myself = reinterpret_cast (host)) + { + auto errorString = LocalRef ((jstring) env->CallObjectMethod (sslError, SslError.toString)); + + myself->owner.pageLoadHadNetworkError (juceString (errorString)); + } + } + + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + CALLBACK (webViewCloseWindowRequest, "webViewCloseWindowRequest", "(JLandroid/webkit/WebView;)V") \ + CALLBACK (webViewCreateWindowRequest, "webViewCreateWindowRequest", "(JLandroid/webkit/WebView;)V") \ + + DECLARE_JNI_CLASS (JuceWebChromeClient, "com/roli/juce/JuceWebView$ChromeClient") + #undef JNI_CLASS_MEMBERS + + static void JNICALL webViewCloseWindowRequest (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/) + { + if (auto* myself = reinterpret_cast (host)) + myself->owner.windowCloseRequest(); + } + + static void JNICALL webViewCreateWindowRequest (JNIEnv*, jobject /*activity*/, jlong host, jobject /*webView*/) + { + if (auto* myself = reinterpret_cast (host)) + myself->owner.newWindowAttemptingToLoad ({}); + } + + //============================================================================== + void webReceivedHttpError (jobject errorResponse) + { + auto* env = getEnv(); + + LocalRef responseClass (env->FindClass ("android/webkit/WebResourceResponse")); + + if (responseClass != 0) + { + jmethodID method = env->GetMethodID (responseClass, "getReasonPhrase", "()Ljava/lang/String;"); + + if (method != 0) + { + auto errorString = LocalRef ((jstring) env->CallObjectMethod (errorResponse, method)); + + owner.pageLoadHadNetworkError (juceString (errorString)); + return; + } + } + + // Should never get here! + jassertfalse; + owner.pageLoadHadNetworkError ({}); + } + + //============================================================================== GlobalRef juceWebChromeClient; GlobalRef juceWebViewClient; std::unique_ptr connectionThread; @@ -496,11 +704,9 @@ void WebBrowserComponent::clearCookies() auto cookieManager = LocalRef (env->CallStaticObjectMethod (AndroidCookieManager, AndroidCookieManager.getInstance)); - const bool apiAtLeast21 = env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion) >= 21; - jmethodID clearCookiesMethod = 0; - if (apiAtLeast21) + if (getAndroidSDKVersion() >= 21) { clearCookiesMethod = env->GetMethodID (AndroidCookieManager, "removeAllCookies", "(Landroid/webkit/ValueCallback;)V"); env->CallVoidMethod (cookieManager, clearCookiesMethod, 0); @@ -512,97 +718,7 @@ void WebBrowserComponent::clearCookies() } } -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewPageLoadStarted, bool, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject url)) -{ - setEnv (env); - - return juce_webViewPageLoadStarted (reinterpret_cast (host), - juceString (static_cast (url))); -} - -bool juce_webViewPageLoadStarted (WebBrowserComponent* browserComponent, const String& url) -{ - return browserComponent->browser->handlePageAboutToLoad (url); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewPageLoadFinished, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject url)) -{ - setEnv (env); - - reinterpret_cast (host)->pageFinishedLoading (juceString (static_cast (url))); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject error)) -{ - setEnv (env); - - jclass errorClass = env->FindClass ("android/webkit/WebResourceError"); - - if (errorClass != 0) - { - jmethodID method = env->GetMethodID (errorClass, "getDescription", "()Ljava/lang/CharSequence;"); - - if (method != 0) - { - auto sequence = LocalRef (env->CallObjectMethod (error, method)); - auto errorString = LocalRef ((jstring) env->CallObjectMethod (sequence, JavaCharSequence.toString)); - - reinterpret_cast (host)->pageLoadHadNetworkError (juceString (errorString)); - return; - } - } - - // Should never get here! - jassertfalse; - reinterpret_cast (host)->pageLoadHadNetworkError ({}); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedHttpError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject errorResponse)) -{ - setEnv (env); - - jclass responseClass = env->FindClass ("android/webkit/WebResourceResponse"); - - if (responseClass != 0) - { - jmethodID method = env->GetMethodID (responseClass, "getReasonPhrase", "()Ljava/lang/String;"); - - if (method != 0) - { - auto errorString = LocalRef ((jstring) env->CallObjectMethod (errorResponse, method)); - - reinterpret_cast (host)->pageLoadHadNetworkError (juceString (errorString)); - return; - } - } - - // Should never get here! - jassertfalse; - reinterpret_cast (host)->pageLoadHadNetworkError ({}); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedSslError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError)) -{ - setEnv (env); - - auto errorString = LocalRef ((jstring) env->CallObjectMethod (sslError, SslError.toString)); - - reinterpret_cast (host)->pageLoadHadNetworkError (juceString (errorString)); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewCloseWindowRequest, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/)) -{ - setEnv (env); - - reinterpret_cast (host)->windowCloseRequest(); -} - -JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewCreateWindowRequest, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/)) -{ - setEnv (env); - - reinterpret_cast (host)->newWindowAttemptingToLoad ({}); -} - - +WebBrowserComponent::Pimpl::JuceWebViewClient16_Class WebBrowserComponent::Pimpl::JuceWebViewClient16; +WebBrowserComponent::Pimpl::JuceWebViewClient21_Class WebBrowserComponent::Pimpl::JuceWebViewClient21; +WebBrowserComponent::Pimpl::JuceWebChromeClient_Class WebBrowserComponent::Pimpl::JuceWebChromeClient; } // namespace juce diff --git a/modules/juce_opengl/native/java/com/roli/juce/JuceOpenGLView.java b/modules/juce_opengl/native/java/com/roli/juce/JuceOpenGLView.java new file mode 100644 index 0000000000..022f131cf6 --- /dev/null +++ b/modules/juce_opengl/native/java/com/roli/juce/JuceOpenGLView.java @@ -0,0 +1,56 @@ +package com.roli.juce; + +import android.content.Context; +import android.graphics.Canvas; +import android.view.SurfaceView; + +public class JuceOpenGLView extends SurfaceView +{ + private long host = 0; + + JuceOpenGLView (Context context, long nativeThis) + { + super (context); + host = nativeThis; + } + + public void cancel () + { + host = 0; + } + + //============================================================================== + @Override + protected void onAttachedToWindow () + { + super.onAttachedToWindow (); + + if (host != 0) + onAttchedWindowNative (host); + } + + @Override + protected void onDetachedFromWindow () + { + if (host != 0) + onDetachedFromWindowNative (host); + + super.onDetachedFromWindow (); + } + + @Override + protected void dispatchDraw (Canvas canvas) + { + super.dispatchDraw (canvas); + + if (host != 0) + onDrawNative (host, canvas); + } + + //============================================================================== + private native void onAttchedWindowNative (long nativeThis); + + private native void onDetachedFromWindowNative (long nativeThis); + + private native void onDrawNative (long nativeThis, Canvas canvas); +} \ No newline at end of file diff --git a/modules/juce_opengl/native/juce_OpenGLExtensions.h b/modules/juce_opengl/native/juce_OpenGLExtensions.h index e45d1f4e8f..3738af9575 100644 --- a/modules/juce_opengl/native/juce_OpenGLExtensions.h +++ b/modules/juce_opengl/native/juce_OpenGLExtensions.h @@ -65,8 +65,7 @@ namespace juce USE_FUNCTION (glUniformMatrix2fv, void, (GLint p1, GLsizei p2, GLboolean p3, const GLfloat* p4), (p1, p2, p3, p4))\ USE_FUNCTION (glUniformMatrix3fv, void, (GLint p1, GLsizei p2, GLboolean p3, const GLfloat* p4), (p1, p2, p3, p4))\ USE_FUNCTION (glUniformMatrix4fv, void, (GLint p1, GLsizei p2, GLboolean p3, const GLfloat* p4), (p1, p2, p3, p4))\ - USE_FUNCTION (glBindAttribLocation, void, (GLuint p1, GLuint p2, const GLchar* p3), (p1, p2, p3))\ - USE_FUNCTION (glDrawBuffers, void, (GLsizei p1, const GLenum* p2), (p1, p2)) + USE_FUNCTION (glBindAttribLocation, void, (GLuint p1, GLuint p2, const GLchar* p3), (p1, p2, p3)) /** @internal This macro contains a list of GL extension functions that need to be dynamically loaded on Windows/Linux. @see OpenGLExtensionFunctions diff --git a/modules/juce_opengl/native/juce_OpenGL_android.h b/modules/juce_opengl/native/juce_OpenGL_android.h index bef9b2f5d4..d0c4d5f809 100644 --- a/modules/juce_opengl/native/juce_OpenGL_android.h +++ b/modules/juce_opengl/native/juce_OpenGL_android.h @@ -27,16 +27,56 @@ namespace juce { -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (getParent, "getParent", "()Landroid/view/ViewParent;") \ - METHOD (layout, "layout", "(IIII)V" ) \ - METHOD (getNativeSurface, "getNativeSurface", "()Landroid/view/Surface;") \ +//============================================================================== +// This byte-code is generated from native/java/com/roli/juce/JuceOpenGLView.java with min sdk version 16 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const uint8 javaJuceOpenGLView[] = +{31,139,8,8,110,106,229,91,0,3,74,117,99,101,79,112,101,110,71,76,86,105,101,119,46,100,101,120,0,109,84,63,104,19,81,24, +255,222,221,75,82,211,244,26,211,70,180,130,68,172,160,80,189,170,21,11,169,82,105,169,16,15,139,180,164,90,187,28,151, +211,92,105,239,98,114,77,139,56,20,151,58,185,40,34,34,184,56,22,167,14,213,77,165,187,179,58,56,58,43,82,7,7,127,239,79, +76,10,61,248,189,223,247,190,255,119,247,222,87,241,215,210,195,23,46,210,241,45,159,207,157,188,253,250,215,169,241,167, +83,63,159,111,216,239,78,127,114,30,230,63,252,229,68,53,34,90,43,143,228,72,63,11,208,29,38,165,79,2,159,53,255,0,24,48, +140,37,3,30,101,106,255,2,203,164,73,180,13,126,111,16,125,4,118,128,47,192,111,32,11,219,16,224,0,211,192,12,112,11,88, +0,92,32,0,238,3,15,128,199,192,19,224,37,240,22,216,1,190,2,187,64,130,171,122,16,9,34,37,116,95,41,141,132,238,191,75, +203,207,80,251,128,150,95,65,78,107,249,13,228,110,45,111,118,232,183,12,149,55,43,107,152,50,151,9,75,175,174,153,211, +220,47,235,115,105,23,107,70,178,242,227,218,143,163,211,62,189,239,215,251,188,100,131,14,105,125,235,93,196,99,104,30, +81,105,241,94,92,234,186,185,234,175,86,32,58,47,35,231,33,205,15,18,89,136,101,210,55,207,85,255,45,171,37,227,13,89, +227,40,150,131,224,168,192,104,150,102,198,225,133,180,231,80,112,20,223,76,236,107,227,221,196,111,90,242,27,48,217,207, +96,43,38,43,114,236,27,51,220,69,156,117,198,12,233,152,253,189,211,240,142,112,12,184,236,141,237,121,103,67,238,153, +222,183,229,228,88,16,6,241,21,98,37,234,43,173,120,254,116,205,15,175,57,229,192,95,61,187,232,54,93,58,226,184,97,165, +30,5,21,219,139,194,216,15,99,123,66,240,90,92,236,48,221,171,187,181,106,224,53,236,9,55,108,186,141,34,13,252,55,53, +145,201,158,89,169,223,117,61,95,100,45,210,49,199,139,150,237,122,180,20,216,139,40,104,239,173,90,36,86,38,163,92,34, +179,92,114,32,56,16,156,18,37,61,55,244,252,37,201,168,64,41,79,117,65,153,74,208,168,185,177,87,157,172,187,171,196,171, +81,35,166,116,232,198,65,211,159,173,6,13,202,69,225,213,56,118,189,170,95,153,141,230,130,176,18,173,82,94,234,132,74, +41,110,72,119,234,143,194,73,95,185,78,213,163,101,237,60,176,159,86,71,100,96,67,89,189,227,177,40,216,99,244,230,82, +214,165,233,51,56,152,41,235,58,13,49,43,101,93,222,152,167,1,82,124,2,188,113,103,12,63,128,227,240,241,245,117,190,109, +242,71,6,25,0,3,18,236,155,201,248,31,96,147,27,252,59,239,209,255,145,117,112,107,110,24,29,179,195,236,152,31,173,115, +47,102,72,130,218,115,36,73,237,89,194,10,202,38,230,9,203,182,239,178,81,80,249,197,140,49,181,143,184,31,84,80,177,35, +250,242,10,89,204,176,127,191,250,1,66,252,4,0,0}; + +//============================================================================== +struct AndroidGLCallbacks +{ + static void attachedToWindow (JNIEnv*, jobject, jlong); + static void detachedFromWindow (JNIEnv*, jobject, jlong); + static void dispatchDraw (JNIEnv*, jobject, jlong, jobject); +}; -DECLARE_JNI_CLASS (NativeSurfaceView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView") +//============================================================================== +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(Landroid/content/Context;J)V") \ + METHOD (getParent, "getParent", "()Landroid/view/ViewParent;") \ + METHOD (getHolder, "getHolder", "()Landroid/view/SurfaceHolder;") \ + METHOD (layout, "layout", "(IIII)V" ) \ + CALLBACK (AndroidGLCallbacks::attachedToWindow, "onAttchedWindowNative", "(J)V") \ + CALLBACK (AndroidGLCallbacks::detachedFromWindow, "onDetachedFromWindowNative", "(J)V") \ + CALLBACK (AndroidGLCallbacks::dispatchDraw, "onDrawNative", "(JLandroid/graphics/Canvas;)V") + +DECLARE_JNI_CLASS_WITH_BYTECODE (JuceOpenGLViewSurface, "com/roli/juce/JuceOpenGLView", 16, javaJuceOpenGLView, sizeof(javaJuceOpenGLView)) #undef JNI_CLASS_MEMBERS //============================================================================== -class OpenGLContext::NativeContext +class OpenGLContext::NativeContext : private SurfaceHolderCallback { public: NativeContext (Component& comp, @@ -58,10 +98,10 @@ public: return; // create a native surface view - surfaceView = GlobalRef (env->CallObjectMethod (android.activity.get(), - JuceAppActivity.createNativeSurfaceView, - reinterpret_cast (this), - false)); + surfaceView = GlobalRef (LocalRef(env->NewObject (JuceOpenGLViewSurface, + JuceOpenGLViewSurface.constructor, + getAppContext().get(), + reinterpret_cast (this)))); if (surfaceView.get() == nullptr) return; @@ -82,7 +122,7 @@ public: { auto env = getEnv(); - if (jobject viewParent = env->CallObjectMethod (surfaceView.get(), NativeSurfaceView.getParent)) + if (jobject viewParent = env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getParent)) env->CallVoidMethod (viewParent, AndroidViewGroup.removeView, surfaceView.get()); } @@ -98,15 +138,22 @@ public: ANativeWindow* window = nullptr; - if (jobject jSurface = env->CallObjectMethod (surfaceView.get(), NativeSurfaceView.getNativeSurface)) + LocalRef holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder)); + + if (holder != nullptr) { - window = ANativeWindow_fromSurface (env, jSurface); + LocalRef jSurface (env->CallObjectMethod (holder.get(), AndroidSurfaceHolder.getSurface)); - // if we didn't succeed the first time, wait briefly and try again.. - if (window == nullptr) + if (jSurface != nullptr) { - Thread::sleep (200); - window = ANativeWindow_fromSurface (env, jSurface); + window = ANativeWindow_fromSurface(env, jSurface.get()); + + // if we didn't succeed the first time, wait 25ms and try again + if (window == nullptr) + { + Thread::sleep (200); + window = ANativeWindow_fromSurface (env, jSurface.get()); + } } } @@ -188,29 +235,20 @@ public: lastBounds = bounds; auto r = bounds * Desktop::getInstance().getDisplays().getMainDisplay().scale; - env->CallVoidMethod (surfaceView.get(), NativeSurfaceView.layout, + env->CallVoidMethod (surfaceView.get(), JuceOpenGLViewSurface.layout, (jint) r.getX(), (jint) r.getY(), (jint) r.getRight(), (jint) r.getBottom()); } } //============================================================================== // Android Surface Callbacks: - - void dispatchDraw (jobject canvas) - { - ignoreUnused (canvas); - - if (juceContext != nullptr) - juceContext->triggerRepaint(); - } - - void surfaceChanged (jobject holder, int format, int width, int height) + void surfaceChanged (LocalRef holder, int format, int width, int height) override { ignoreUnused (holder, format, width, height); } - void surfaceCreated (jobject holder); - void surfaceDestroyed (jobject holder); + void surfaceCreated (LocalRef holder) override; + void surfaceDestroyed (LocalRef holder) override; //============================================================================== struct Locker { Locker (NativeContext&) {} }; @@ -218,6 +256,40 @@ public: Component& component; private: + //============================================================================== + friend struct AndroidGLCallbacks; + + void attachedToWindow() + { + auto* env = getEnv(); + + LocalRef holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder)); + + if (surfaceHolderCallback == nullptr) + surfaceHolderCallback = GlobalRef (CreateJavaInterface (this, "android/view/SurfaceHolder$Callback")); + + env->CallVoidMethod (holder, AndroidSurfaceHolder.addCallback, surfaceHolderCallback.get()); + } + + void detachedFromWindow() + { + if (surfaceHolderCallback != nullptr) + { + auto* env = getEnv(); + + LocalRef holder (env->CallObjectMethod (surfaceView.get(), JuceOpenGLViewSurface.getHolder)); + + env->CallVoidMethod (holder.get(), AndroidSurfaceHolder.removeCallback, surfaceHolderCallback.get()); + surfaceHolderCallback.clear(); + } + } + + void dispatchDraw (jobject /*canvas*/) + { + if (juceContext != nullptr) + juceContext->triggerRepaint(); + } + //============================================================================== bool initEGLDisplay() { @@ -271,6 +343,8 @@ private: EGLSurface surface; EGLContext context; + GlobalRef surfaceHolderCallback; + static EGLDisplay display; static EGLConfig config; @@ -278,36 +352,22 @@ private: }; //============================================================================== -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), dispatchDrawNative, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject canvas)) -{ - ignoreUnused (nativeView); - setEnv (env); - reinterpret_cast (host)->dispatchDraw (canvas); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceChangedNative, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder, jint format, jint width, jint height)) +void AndroidGLCallbacks::attachedToWindow (JNIEnv*, jobject /*this*/, jlong host) { - ignoreUnused (nativeView); - setEnv (env); - reinterpret_cast (host)->surfaceChanged (holder, format, width, height); + if (auto* nativeContext = reinterpret_cast (host)) + nativeContext->attachedToWindow(); } -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceCreatedNative, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder)) +void AndroidGLCallbacks::detachedFromWindow (JNIEnv*, jobject /*this*/, jlong host) { - ignoreUnused (nativeView); - setEnv (env); - reinterpret_cast (host)->surfaceCreated (holder); + if (auto* nativeContext = reinterpret_cast (host)) + nativeContext->detachedFromWindow(); } -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceDestroyedNative, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder)) +void AndroidGLCallbacks::dispatchDraw (JNIEnv*, jobject /*this*/, jlong host, jobject canvas) { - ignoreUnused (nativeView); - setEnv (env); - reinterpret_cast (host)->surfaceDestroyed (holder); + if (auto* nativeContext = reinterpret_cast (host)) + nativeContext->dispatchDraw (canvas); } //============================================================================== diff --git a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp index a45d714d35..0746ee36d9 100644 --- a/modules/juce_opengl/opengl/juce_OpenGLContext.cpp +++ b/modules/juce_opengl/opengl/juce_OpenGLContext.cpp @@ -1218,7 +1218,7 @@ void OpenGLContext::copyTexture (const Rectangle& targetClipArea, EGLDisplay OpenGLContext::NativeContext::display = EGL_NO_DISPLAY; EGLDisplay OpenGLContext::NativeContext::config; -void OpenGLContext::NativeContext::surfaceCreated (jobject holder) +void OpenGLContext::NativeContext::surfaceCreated (LocalRef holder) { ignoreUnused (holder); @@ -1235,7 +1235,7 @@ void OpenGLContext::NativeContext::surfaceCreated (jobject holder) } } -void OpenGLContext::NativeContext::surfaceDestroyed (jobject holder) +void OpenGLContext::NativeContext::surfaceDestroyed (LocalRef holder) { ignoreUnused (holder); diff --git a/modules/juce_video/native/java/com/roli/juce/CameraCaptureSessionCaptureCallback.java b/modules/juce_video/native/java/com/roli/juce/CameraCaptureSessionCaptureCallback.java new file mode 100644 index 0000000000..db5adf74f3 --- /dev/null +++ b/modules/juce_video/native/java/com/roli/juce/CameraCaptureSessionCaptureCallback.java @@ -0,0 +1,65 @@ +package com.roli.juce; + +import android.hardware.camera2.CameraCaptureSession; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.TotalCaptureResult; +import android.hardware.camera2.CaptureFailure; +import android.hardware.camera2.CaptureResult; + +public class CameraCaptureSessionCaptureCallback extends CameraCaptureSession.CaptureCallback +{ + private native void cameraCaptureSessionCaptureCompleted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result); + private native void cameraCaptureSessionCaptureFailed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureFailure failure); + private native void cameraCaptureSessionCaptureProgressed (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult); + private native void cameraCaptureSessionCaptureStarted (long host, boolean isPreview, CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber); + private native void cameraCaptureSessionCaptureSequenceAborted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId); + private native void cameraCaptureSessionCaptureSequenceCompleted (long host, boolean isPreview, CameraCaptureSession session, int sequenceId, long frameNumber); + + CameraCaptureSessionCaptureCallback (long hostToUse, boolean shouldBePreview) + { + host = hostToUse; + preview = shouldBePreview; + } + + @Override + public void onCaptureCompleted (CameraCaptureSession session, CaptureRequest request, + TotalCaptureResult result) + { + cameraCaptureSessionCaptureCompleted (host, preview, session, request, result); + } + + @Override + public void onCaptureFailed (CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) + { + cameraCaptureSessionCaptureFailed (host, preview, session, request, failure); + } + + @Override + public void onCaptureProgressed (CameraCaptureSession session, CaptureRequest request, + CaptureResult partialResult) + { + cameraCaptureSessionCaptureProgressed (host, preview, session, request, partialResult); + } + + @Override + public void onCaptureSequenceAborted (CameraCaptureSession session, int sequenceId) + { + cameraCaptureSessionCaptureSequenceAborted (host, preview, session, sequenceId); + } + + @Override + public void onCaptureSequenceCompleted (CameraCaptureSession session, int sequenceId, long frameNumber) + { + cameraCaptureSessionCaptureSequenceCompleted (host, preview, session, sequenceId, frameNumber); + } + + @Override + public void onCaptureStarted (CameraCaptureSession session, CaptureRequest request, long timestamp, + long frameNumber) + { + cameraCaptureSessionCaptureStarted (host, preview, session, request, timestamp, frameNumber); + } + + private long host; + private boolean preview; +} diff --git a/modules/juce_video/native/java/com/roli/juce/CameraCaptureSessionStateCallback.java b/modules/juce_video/native/java/com/roli/juce/CameraCaptureSessionStateCallback.java new file mode 100644 index 0000000000..3cb782c024 --- /dev/null +++ b/modules/juce_video/native/java/com/roli/juce/CameraCaptureSessionStateCallback.java @@ -0,0 +1,53 @@ +package com.roli.juce; + +import android.hardware.camera2.CameraCaptureSession; + +public class CameraCaptureSessionStateCallback extends CameraCaptureSession.StateCallback +{ + private native void cameraCaptureSessionActive (long host, CameraCaptureSession session); + + private native void cameraCaptureSessionClosed (long host, CameraCaptureSession session); + + private native void cameraCaptureSessionConfigureFailed (long host, CameraCaptureSession session); + + private native void cameraCaptureSessionConfigured (long host, CameraCaptureSession session); + + private native void cameraCaptureSessionReady (long host, CameraCaptureSession session); + + CameraCaptureSessionStateCallback (long hostToUse) + { + host = hostToUse; + } + + @Override + public void onActive (CameraCaptureSession session) + { + cameraCaptureSessionActive (host, session); + } + + @Override + public void onClosed (CameraCaptureSession session) + { + cameraCaptureSessionClosed (host, session); + } + + @Override + public void onConfigureFailed (CameraCaptureSession session) + { + cameraCaptureSessionConfigureFailed (host, session); + } + + @Override + public void onConfigured (CameraCaptureSession session) + { + cameraCaptureSessionConfigured (host, session); + } + + @Override + public void onReady (CameraCaptureSession session) + { + cameraCaptureSessionReady (host, session); + } + + private long host; +} diff --git a/modules/juce_video/native/java/com/roli/juce/CameraDeviceStateCallback.java b/modules/juce_video/native/java/com/roli/juce/CameraDeviceStateCallback.java new file mode 100644 index 0000000000..509ae621f8 --- /dev/null +++ b/modules/juce_video/native/java/com/roli/juce/CameraDeviceStateCallback.java @@ -0,0 +1,43 @@ +package com.roli.juce; + +import android.hardware.camera2.CameraDevice; + +public class CameraDeviceStateCallback extends CameraDevice.StateCallback +{ + private native void cameraDeviceStateClosed (long host, CameraDevice camera); + private native void cameraDeviceStateDisconnected (long host, CameraDevice camera); + private native void cameraDeviceStateError (long host, CameraDevice camera, int error); + private native void cameraDeviceStateOpened (long host, CameraDevice camera); + + CameraDeviceStateCallback (long hostToUse) + { + host = hostToUse; + } + + @Override + public void onClosed (CameraDevice camera) + { + cameraDeviceStateClosed (host, camera); + } + + @Override + public void onDisconnected (CameraDevice camera) + { + cameraDeviceStateDisconnected (host, camera); + } + + @Override + public void onError (CameraDevice camera, int error) + { + cameraDeviceStateError (host, camera, error); + } + + @Override + public void onOpened (CameraDevice camera) + { + cameraDeviceStateOpened (host, camera); + } + + private long host; +} + diff --git a/modules/juce_video/native/java/com/roli/juce/JuceOrientationEventListener.java b/modules/juce_video/native/java/com/roli/juce/JuceOrientationEventListener.java new file mode 100644 index 0000000000..89769b6092 --- /dev/null +++ b/modules/juce_video/native/java/com/roli/juce/JuceOrientationEventListener.java @@ -0,0 +1,24 @@ +package com.roli.juce; + +import android.view.OrientationEventListener; +import android.content.Context; + +public class JuceOrientationEventListener extends OrientationEventListener +{ + private native void deviceOrientationChanged (long host, int orientation); + + public JuceOrientationEventListener (long hostToUse, Context context, int rate) + { + super (context, rate); + + host = hostToUse; + } + + @Override + public void onOrientationChanged (int orientation) + { + deviceOrientationChanged (host, orientation); + } + + private long host; +} diff --git a/modules/juce_video/native/java/com/roli/juce/MediaControllerCallback.java b/modules/juce_video/native/java/com/roli/juce/MediaControllerCallback.java new file mode 100644 index 0000000000..8e02e9c263 --- /dev/null +++ b/modules/juce_video/native/java/com/roli/juce/MediaControllerCallback.java @@ -0,0 +1,53 @@ +package com.roli.juce; + +import android.media.session.MediaController; +import android.media.session.MediaSession; +import android.media.MediaMetadata; +import android.media.session.PlaybackState; + +import java.util.List; + +//============================================================================== +public class MediaControllerCallback extends MediaController.Callback +{ + private native void mediaControllerAudioInfoChanged (long host, MediaController.PlaybackInfo info); + private native void mediaControllerMetadataChanged (long host, MediaMetadata metadata); + private native void mediaControllerPlaybackStateChanged (long host, PlaybackState state); + private native void mediaControllerSessionDestroyed (long host); + + MediaControllerCallback (long hostToUse) + { + host = hostToUse; + } + + @Override + public void onAudioInfoChanged (MediaController.PlaybackInfo info) + { + mediaControllerAudioInfoChanged (host, info); + } + + @Override + public void onMetadataChanged (MediaMetadata metadata) + { + mediaControllerMetadataChanged (host, metadata); + } + + @Override + public void onPlaybackStateChanged (PlaybackState state) + { + mediaControllerPlaybackStateChanged (host, state); + } + + @Override + public void onQueueChanged (List queue) + { + } + + @Override + public void onSessionDestroyed () + { + mediaControllerSessionDestroyed (host); + } + + private long host; +} diff --git a/modules/juce_video/native/java/com/roli/juce/MediaSessionCallback.java b/modules/juce_video/native/java/com/roli/juce/MediaSessionCallback.java new file mode 100644 index 0000000000..b93e5979e0 --- /dev/null +++ b/modules/juce_video/native/java/com/roli/juce/MediaSessionCallback.java @@ -0,0 +1,78 @@ +package com.roli.juce; + +import android.media.session.MediaSession; + +import java.lang.String; + +import android.os.Bundle; +import android.content.Intent; + +import java.util.List; + +//============================================================================== +public class MediaSessionCallback extends MediaSession.Callback +{ + private native void mediaSessionPause (long host); + private native void mediaSessionPlay (long host); + private native void mediaSessionPlayFromMediaId (long host, String mediaId, Bundle extras); + private native void mediaSessionSeekTo (long host, long pos); + private native void mediaSessionStop (long host); + + MediaSessionCallback (long hostToUse) + { + host = hostToUse; + } + + @Override + public void onPause () + { + mediaSessionPause (host); + } + + @Override + public void onPlay () + { + mediaSessionPlay (host); + } + + @Override + public void onPlayFromMediaId (String mediaId, Bundle extras) + { + mediaSessionPlayFromMediaId (host, mediaId, extras); + } + + @Override + public void onSeekTo (long pos) + { + mediaSessionSeekTo (host, pos); + } + + @Override + public void onStop () + { + mediaSessionStop (host); + } + + @Override + public void onFastForward () {} + + @Override + public boolean onMediaButtonEvent (Intent mediaButtonIntent) + { + return true; + } + + @Override + public void onRewind () {} + + @Override + public void onSkipToNext () {} + + @Override + public void onSkipToPrevious () {} + + @Override + public void onSkipToQueueItem (long id) {} + + private long host; +} diff --git a/modules/juce_video/native/java/com/roli/juce/SystemVolumeObserver.java b/modules/juce_video/native/java/com/roli/juce/SystemVolumeObserver.java new file mode 100644 index 0000000000..bead8a05ae --- /dev/null +++ b/modules/juce_video/native/java/com/roli/juce/SystemVolumeObserver.java @@ -0,0 +1,37 @@ +package com.roli.juce; + +import android.database.ContentObserver; +import android.app.Activity; +import android.net.Uri; + +//============================================================================== +public class SystemVolumeObserver extends ContentObserver +{ + private native void mediaSessionSystemVolumeChanged (long host); + + SystemVolumeObserver (Activity activityToUse, long hostToUse) + { + super (null); + + activity = activityToUse; + host = hostToUse; + } + + void setEnabled (boolean shouldBeEnabled) + { + if (shouldBeEnabled) + activity.getApplicationContext ().getContentResolver ().registerContentObserver (android.provider.Settings.System.CONTENT_URI, true, this); + else + activity.getApplicationContext ().getContentResolver ().unregisterContentObserver (this); + } + + @Override + public void onChange (boolean selfChange, Uri uri) + { + if (uri.toString ().startsWith ("content://settings/system/volume_music")) + mediaSessionSystemVolumeChanged (host); + } + + private Activity activity; + private long host; +} diff --git a/modules/juce_video/native/juce_android_CameraDevice.h b/modules/juce_video/native/juce_android_CameraDevice.h index 9ec4529c71..84044f90fd 100644 --- a/modules/juce_video/native/juce_android_CameraDevice.h +++ b/modules/juce_video/native/juce_android_CameraDevice.h @@ -24,45 +24,114 @@ ============================================================================== */ -#if __ANDROID_API__ >= 21 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +//============================================================================== +// This byte-code is generated from: +// +// native/java/com/roli/juce/CameraCaptureSessionCaptureCallback.java +// native/java/com/roli/juce/CameraCaptureSessionStateCallback.java +// native/java/com/roli/juce/CameraDeviceStateCallback.java +// native/java/com/roli/juce/JuceOrientationEventListener.java +// +// files with min sdk version 21 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const uint8 CameraSupportByteCode[] = { +31,139,8,8,45,45,227,91,0,3,67,97,109,101,114,97,83,117,112,112,111,114,116,46,100,101,120,0,149,152,93,108,28,213,21, +199,207,157,157,221,89,239,174,215,227,181,243,73,18,236,124,216,14,196,108,190,140,162,174,227,196,56,9,216,93,197,169, +215,118,35,3,45,147,221,73,60,116,189,179,204,174,151,84,173,74,136,168,8,47,85,80,105,1,9,33,16,1,209,7,36,183,4,194,3, +15,169,138,10,85,133,148,34,170,246,33,149,120,40,60,180,84,138,80,133,242,192,67,255,247,99,236,217,245,218,53,150,126, +251,63,51,231,220,115,239,61,247,206,120,102,10,246,249,216,222,3,3,244,159,203,87,222,62,245,197,223,255,124,233,193,91, +39,95,59,122,248,179,137,191,189,250,253,83,157,191,127,189,47,69,84,38,162,243,211,7,97,201,191,75,237,68,167,73,158, +239,0,55,24,209,38,232,22,141,40,12,125,32,68,212,199,253,80,29,186,128,159,219,9,162,189,112,190,23,37,186,14,254,5,82, +45,68,251,193,0,56,4,30,4,53,112,13,124,13,122,99,68,63,0,79,131,223,129,127,0,35,78,116,47,248,33,120,14,124,8,190,2,93, +200,191,3,244,130,187,121,95,96,0,100,192,81,112,63,24,7,167,193,35,160,0,28,224,130,26,248,41,120,2,60,13,94,3,127,2,95, +130,142,86,162,35,96,14,60,11,222,2,159,128,175,64,107,146,168,7,28,7,15,131,26,120,2,188,8,94,1,191,1,239,130,247,193, +135,224,47,224,115,240,53,136,183,17,237,2,195,32,7,30,6,101,240,51,240,11,240,60,120,21,92,3,215,193,199,224,38,248,28, +252,27,252,23,124,3,162,38,81,59,216,14,250,76,89,111,190,6,6,64,137,9,101,36,148,141,80,34,194,116,8,195,38,116,79,104, +74,8,39,44,37,165,212,58,118,130,117,96,61,232,81,218,174,214,124,131,178,23,144,120,163,178,223,51,228,186,115,251,58, +236,59,148,253,17,236,205,202,254,52,96,223,12,196,252,19,246,22,101,223,130,189,85,217,183,97,111,83,182,142,9,220,169, +236,78,216,93,202,222,25,176,15,6,236,99,176,183,43,123,18,118,183,178,31,10,156,47,192,222,161,236,34,236,157,202,62,15, +123,151,178,159,10,216,151,3,246,75,129,156,111,6,114,46,68,121,93,25,13,138,250,166,104,92,212,88,30,183,41,53,149,134, +84,61,117,165,17,165,9,177,34,188,125,84,105,146,118,11,77,208,93,66,91,233,110,161,45,180,71,104,140,250,133,198,233,30, +161,235,233,136,208,78,58,42,116,29,13,11,109,167,251,212,184,70,132,118,208,49,49,62,77,244,147,196,110,233,83,154,86, +186,87,233,62,165,251,133,154,52,164,244,184,210,19,74,239,87,154,21,243,149,121,219,48,179,3,74,15,10,53,104,64,29,223, +43,116,131,200,195,245,1,161,27,105,84,29,143,137,122,201,10,153,168,216,33,85,191,239,146,220,191,44,80,71,166,244,83, +147,212,124,228,57,191,190,154,210,148,218,200,109,202,239,215,221,207,115,66,249,77,229,79,52,172,143,167,252,97,100, +230,254,3,166,188,190,202,38,143,159,193,37,244,80,39,67,239,9,248,249,181,119,216,148,57,114,23,137,166,158,98,100,60, +105,252,202,120,195,184,86,51,194,20,140,59,177,98,92,164,46,110,124,197,56,67,196,197,48,19,62,214,211,166,188,214,115, +30,226,170,136,123,204,248,57,251,117,45,18,21,81,114,61,249,184,31,241,243,61,139,184,95,34,238,178,113,133,189,163,255, +177,22,109,17,81,41,244,202,239,27,142,41,107,154,251,45,226,222,70,220,130,241,129,241,87,253,139,48,110,60,181,88,76, +196,234,232,155,215,164,98,202,122,149,77,38,106,162,9,159,38,214,226,39,202,151,235,210,168,124,52,69,218,190,37,223, +197,58,95,71,157,239,153,58,95,103,157,239,114,157,111,93,157,239,249,58,223,122,229,147,227,124,121,113,156,154,24,103, +40,48,206,55,22,219,133,208,110,107,93,206,183,234,124,219,132,47,140,156,252,126,123,53,232,155,188,179,174,221,251,117, +237,186,132,47,34,90,18,253,193,31,203,222,16,13,135,103,82,122,96,44,31,47,182,211,209,174,71,180,243,247,59,83,104,129, +235,129,169,221,46,207,133,149,26,202,103,4,124,41,145,159,107,152,228,255,11,223,246,175,13,121,220,66,76,100,90,58,142, +214,197,183,136,61,18,60,142,171,62,252,113,133,149,29,86,99,244,251,144,255,167,100,254,144,178,163,139,49,50,175,111, +199,85,27,67,229,226,26,25,116,74,78,117,136,250,70,172,57,219,179,70,172,114,117,222,179,115,118,165,226,184,37,117,52, +98,21,139,103,172,252,143,238,121,212,170,89,212,211,44,50,87,181,170,13,113,219,100,220,49,187,230,228,237,38,126,54,74, +108,140,186,199,230,243,246,184,231,216,37,68,32,209,241,26,172,172,83,169,218,37,219,147,129,155,178,86,169,224,185,78, +33,157,119,75,56,95,77,143,112,61,95,205,208,145,69,215,172,229,21,30,183,60,59,157,23,189,238,79,55,27,229,206,134,9, +101,232,240,183,76,80,55,143,12,165,191,93,243,12,13,252,191,6,178,92,141,253,244,174,173,89,134,118,175,22,40,134,114, +194,114,138,144,181,132,78,216,143,205,219,21,148,185,111,13,161,149,249,34,34,251,87,142,156,116,171,86,177,33,124,105, +94,53,199,126,60,189,210,62,200,208,129,108,222,157,75,123,110,209,73,63,138,13,211,180,186,203,22,119,223,26,26,53,20, +122,119,211,38,77,246,112,134,246,52,132,174,182,145,51,196,166,73,155,30,5,99,20,154,30,27,229,63,89,210,241,35,204,25, +10,227,7,118,68,200,24,25,92,179,99,99,242,68,54,139,134,89,4,34,66,159,230,254,240,180,240,226,0,62,54,67,17,89,101,218, +156,111,50,199,225,124,213,169,217,180,179,153,207,47,154,59,87,46,218,85,187,64,221,171,68,241,189,131,144,93,171,132, +156,242,220,115,30,206,32,236,174,85,194,114,124,107,149,242,246,240,25,215,227,189,238,89,67,236,210,24,183,175,22,93, +181,68,198,166,149,24,41,186,124,100,59,154,250,220,210,89,231,220,210,44,183,173,26,84,160,77,205,252,19,182,85,248,49, +109,200,47,219,54,178,227,173,203,28,199,156,10,238,106,37,59,207,199,188,126,153,251,184,231,185,94,147,124,227,101,108, +171,2,25,121,121,39,164,141,5,225,11,236,191,145,89,171,116,14,33,97,91,164,48,206,202,235,158,226,103,61,36,59,57,63, +119,198,246,72,159,117,43,85,106,225,191,147,238,84,197,166,232,226,110,73,53,217,27,109,141,59,161,163,217,186,111,92, +113,149,55,175,178,166,230,178,21,140,46,174,87,251,242,213,73,212,173,69,210,45,213,21,210,192,245,39,166,141,28,170,84, +157,176,150,87,199,240,151,44,238,46,57,169,181,140,17,56,86,81,222,166,200,40,123,54,191,63,145,238,161,244,100,120,242, +190,72,17,79,250,99,21,53,155,81,36,172,200,141,64,109,149,89,119,190,88,184,15,149,81,141,171,179,78,133,90,170,206,28, +218,90,115,101,234,208,134,166,141,228,96,127,63,109,9,77,77,228,140,228,85,218,14,35,195,141,221,48,198,185,145,214,166, +38,141,228,235,52,16,154,154,28,228,39,6,245,169,137,211,176,62,161,36,27,226,205,105,29,155,50,146,51,116,135,148,110, +41,189,82,250,165,180,170,208,78,214,199,15,55,75,233,210,250,190,195,181,71,30,38,66,67,135,190,103,36,15,243,176,147, +252,4,105,120,120,214,152,166,95,184,160,47,196,216,69,60,229,53,33,206,110,196,24,187,13,94,137,51,246,17,184,5,94,72, +16,139,132,53,173,29,109,63,72,52,107,183,129,125,150,96,236,27,240,66,43,99,87,193,13,188,252,133,245,144,182,5,109,190, +108,109,140,239,102,151,146,140,93,1,215,193,77,60,194,105,76,215,118,61,121,65,191,157,228,254,94,246,92,155,124,175, +241,159,235,124,245,191,177,240,103,30,255,59,11,127,38,242,191,181,248,239,250,252,123,11,87,255,155,75,132,150,190,187, +176,46,249,94,207,191,189,48,83,190,187,243,119,120,173,75,230,231,223,99,66,42,134,191,211,240,151,105,222,86,188,79, +153,114,28,252,123,207,255,0,68,14,12,167,40,18,0,0}; + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (valueOf, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$CompressFormat;") -DECLARE_JNI_CLASS (AndroidBitmapCompressFormat, "android/graphics/Bitmap$CompressFormat") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBitmapCompressFormat, "android/graphics/Bitmap$CompressFormat", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (close, "close", "()V") \ METHOD (createCaptureRequest, "createCaptureRequest", "(I)Landroid/hardware/camera2/CaptureRequest$Builder;") \ METHOD (createCaptureSession, "createCaptureSession", "(Ljava/util/List;Landroid/hardware/camera2/CameraCaptureSession$StateCallback;Landroid/os/Handler;)V") -DECLARE_JNI_CLASS (AndroidCameraDevice, "android/hardware/camera2/CameraDevice") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidCameraDevice, "android/hardware/camera2/CameraDevice", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (close, "close", "()V") \ METHOD (getPlanes, "getPlanes", "()[Landroid/media/Image$Plane;") -DECLARE_JNI_CLASS (AndroidImage, "android/media/Image") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidImage, "android/media/Image", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getBuffer, "getBuffer", "()Ljava/nio/ByteBuffer;") -DECLARE_JNI_CLASS (AndroidImagePlane, "android/media/Image$Plane") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidImagePlane, "android/media/Image$Plane", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (acquireLatestImage, "acquireLatestImage", "()Landroid/media/Image;") \ METHOD (close, "close", "()V") \ METHOD (getSurface, "getSurface", "()Landroid/view/Surface;") \ METHOD (setOnImageAvailableListener, "setOnImageAvailableListener", "(Landroid/media/ImageReader$OnImageAvailableListener;Landroid/os/Handler;)V") \ STATICMETHOD (newInstance, "newInstance", "(IIII)Landroid/media/ImageReader;") -DECLARE_JNI_CLASS (AndroidImageReader, "android/media/ImageReader") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidImageReader, "android/media/ImageReader", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (getSurface, "getSurface", "()Landroid/view/Surface;") \ METHOD (prepare, "prepare", "()V") \ @@ -82,71 +151,59 @@ DECLARE_JNI_CLASS (AndroidImageReader, "android/media/ImageReader") METHOD (start, "start", "()V") \ METHOD (stop, "stop", "()V") -DECLARE_JNI_CLASS (AndroidMediaRecorder, "android/media/MediaRecorder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaRecorder, "android/media/MediaRecorder", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Landroid/content/Context;)V") \ METHOD (getSurfaceTexture, "getSurfaceTexture", "()Landroid/graphics/SurfaceTexture;") \ METHOD (isAvailable, "isAvailable", "()Z") \ METHOD (setSurfaceTextureListener, "setSurfaceTextureListener", "(Landroid/view/TextureView$SurfaceTextureListener;)V") \ METHOD (setTransform, "setTransform", "(Landroid/graphics/Matrix;)V") -DECLARE_JNI_CLASS (AndroidTextureView, "android/view/TextureView") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidTextureView, "android/view/TextureView", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Landroid/graphics/SurfaceTexture;)V") -DECLARE_JNI_CLASS (AndroidSurface, "android/view/Surface") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidSurface, "android/view/Surface", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (setDefaultBufferSize, "setDefaultBufferSize", "(II)V") -DECLARE_JNI_CLASS (AndroidSurfaceTexture, "android/graphics/SurfaceTexture") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidSurfaceTexture, "android/graphics/SurfaceTexture", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getOutputSizesForClass, "getOutputSizes", "(Ljava/lang/Class;)[Landroid/util/Size;") \ METHOD (getOutputSizesForFormat, "getOutputSizes", "(I)[Landroid/util/Size;") \ METHOD (isOutputSupportedFor, "isOutputSupportedFor", "(I)Z") \ METHOD (isOutputSupportedForSurface, "isOutputSupportedFor", "(Landroid/view/Surface;)Z") -DECLARE_JNI_CLASS (AndroidStreamConfigurationMap, "android/hardware/camera2/params/StreamConfigurationMap") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidStreamConfigurationMap, "android/hardware/camera2/params/StreamConfigurationMap", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (toByteArray, "toByteArray", "()[B") \ METHOD (size, "size", "()I") -DECLARE_JNI_CLASS (ByteArrayOutputStream, "java/io/ByteArrayOutputStream") +DECLARE_JNI_CLASS_WITH_MIN_SDK (ByteArrayOutputStream, "java/io/ByteArrayOutputStream", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (abortCaptures, "abortCaptures", "()V") \ METHOD (capture, "capture", "(Landroid/hardware/camera2/CaptureRequest;Landroid/hardware/camera2/CameraCaptureSession$CaptureCallback;Landroid/os/Handler;)I") \ METHOD (close, "close", "()V") \ METHOD (setRepeatingRequest, "setRepeatingRequest", "(Landroid/hardware/camera2/CaptureRequest;Landroid/hardware/camera2/CameraCaptureSession$CaptureCallback;Landroid/os/Handler;)I") \ METHOD (stopRepeating, "stopRepeating", "()V") -DECLARE_JNI_CLASS (CameraCaptureSession, "android/hardware/camera2/CameraCaptureSession") +DECLARE_JNI_CLASS_WITH_MIN_SDK (CameraCaptureSession, "android/hardware/camera2/CameraCaptureSession", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";JZ)V") - -DECLARE_JNI_CLASS (CameraCaptureSessionCaptureCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$CameraCaptureSessionCaptureCallback") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") - -DECLARE_JNI_CLASS (CameraCaptureSessionStateCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$CameraCaptureSessionStateCallback") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (get, "get", "(Landroid/hardware/camera2/CameraCharacteristics$Key;)Ljava/lang/Object;") \ METHOD (getKeys, "getKeys", "()Ljava/util/List;") \ STATICFIELD (CONTROL_AF_AVAILABLE_MODES, "CONTROL_AF_AVAILABLE_MODES", "Landroid/hardware/camera2/CameraCharacteristics$Key;") \ @@ -154,63 +211,47 @@ DECLARE_JNI_CLASS (CameraCaptureSessionStateCallback, JUCE_ANDROID_ACTIVITY_CLAS STATICFIELD (SCALER_STREAM_CONFIGURATION_MAP, "SCALER_STREAM_CONFIGURATION_MAP", "Landroid/hardware/camera2/CameraCharacteristics$Key;") \ STATICFIELD (SENSOR_ORIENTATION, "SENSOR_ORIENTATION", "Landroid/hardware/camera2/CameraCharacteristics$Key;") -DECLARE_JNI_CLASS (CameraCharacteristics, "android/hardware/camera2/CameraCharacteristics") +DECLARE_JNI_CLASS_WITH_MIN_SDK (CameraCharacteristics, "android/hardware/camera2/CameraCharacteristics", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getName, "getName", "()Ljava/lang/String;") -DECLARE_JNI_CLASS (CameraCharacteristicsKey, "android/hardware/camera2/CameraCharacteristics$Key") +DECLARE_JNI_CLASS_WITH_MIN_SDK (CameraCharacteristicsKey, "android/hardware/camera2/CameraCharacteristics$Key", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") - -DECLARE_JNI_CLASS (CameraDeviceStateCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$CameraDeviceStateCallback") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getCameraCharacteristics, "getCameraCharacteristics", "(Ljava/lang/String;)Landroid/hardware/camera2/CameraCharacteristics;") \ METHOD (getCameraIdList, "getCameraIdList", "()[Ljava/lang/String;") \ METHOD (openCamera, "openCamera", "(Ljava/lang/String;Landroid/hardware/camera2/CameraDevice$StateCallback;Landroid/os/Handler;)V") -DECLARE_JNI_CLASS (CameraManager, "android/hardware/camera2/CameraManager") +DECLARE_JNI_CLASS_WITH_MIN_SDK (CameraManager, "android/hardware/camera2/CameraManager", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICFIELD (CONTROL_AE_PRECAPTURE_TRIGGER, "CONTROL_AE_PRECAPTURE_TRIGGER", "Landroid/hardware/camera2/CaptureRequest$Key;") \ STATICFIELD (CONTROL_AF_MODE, "CONTROL_AF_MODE", "Landroid/hardware/camera2/CaptureRequest$Key;") \ STATICFIELD (CONTROL_AF_TRIGGER, "CONTROL_AF_TRIGGER", "Landroid/hardware/camera2/CaptureRequest$Key;") \ STATICFIELD (CONTROL_MODE, "CONTROL_MODE", "Landroid/hardware/camera2/CaptureRequest$Key;") -DECLARE_JNI_CLASS (CaptureRequest, "android/hardware/camera2/CaptureRequest") +DECLARE_JNI_CLASS_WITH_MIN_SDK (CaptureRequest, "android/hardware/camera2/CaptureRequest", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (addTarget, "addTarget", "(Landroid/view/Surface;)V") \ METHOD (build, "build", "()Landroid/hardware/camera2/CaptureRequest;") \ METHOD (set, "set", "(Landroid/hardware/camera2/CaptureRequest$Key;Ljava/lang/Object;)V") -DECLARE_JNI_CLASS (CaptureRequestBuilder, "android/hardware/camera2/CaptureRequest$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (CaptureRequestBuilder, "android/hardware/camera2/CaptureRequest$Builder", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (get, "get", "(Landroid/hardware/camera2/CaptureResult$Key;)Ljava/lang/Object;") \ STATICFIELD (CONTROL_AE_STATE, "CONTROL_AE_STATE", "Landroid/hardware/camera2/CaptureResult$Key;") \ STATICFIELD (CONTROL_AF_STATE, "CONTROL_AF_STATE", "Landroid/hardware/camera2/CaptureResult$Key;") -DECLARE_JNI_CLASS (CaptureResult, "android/hardware/camera2/CaptureResult") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (canDetectOrientation, "canDetectOrientation", "()Z") \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";JLandroid/content/Context;I)V") \ - METHOD (disable, "disable", "()V") \ - METHOD (enable, "enable", "()V") - -DECLARE_JNI_CLASS (OrientationEventListener, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceOrientationEventListener") +DECLARE_JNI_CLASS_WITH_MIN_SDK (CaptureResult, "android/hardware/camera2/CaptureResult", 21) #undef JNI_CLASS_MEMBERS -#endif //============================================================================== class AndroidRunnable : public juce::AndroidInterfaceImplementer @@ -444,65 +485,37 @@ private: //============================================================================== struct CameraDevice::Pimpl -#if __ANDROID_API__ >= 21 - : private AppPausedResumedListener::Owner -#endif + : private ActivityLifecycleCallbacks { using InternalOpenCameraResultCallback = std::function; Pimpl (CameraDevice& ownerToUse, const String& cameraIdToUse, int /*index*/, int minWidthToUse, int minHeightToUse, int maxWidthToUse, int maxHeightToUse, bool /*useHighQuality*/) - #if __ANDROID_API__ >= 21 : owner (ownerToUse), minWidth (minWidthToUse), minHeight (minHeightToUse), maxWidth (maxWidthToUse), maxHeight (maxHeightToUse), cameraId (cameraIdToUse), - appPausedResumedListener (*this), - appPausedResumedListenerNative (CreateJavaInterface (&appPausedResumedListener, - JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener").get()), + activityLifeListener (CreateJavaInterface (this, "android/app/Application$ActivityLifecycleCallbacks")), cameraManager (initialiseCameraManager()), cameraCharacteristics (initialiseCameraCharacteristics (cameraManager, cameraId)), streamConfigurationMap (cameraCharacteristics), previewDisplay (streamConfigurationMap.getPreviewBufferSize()), deviceOrientationChangeListener (previewDisplay) - #endif { - #if __ANDROID_API__ >= 21 startBackgroundThread(); - #endif } ~Pimpl() { - #if __ANDROID_API__ >= 21 - getEnv()->CallVoidMethod (android.activity, JuceAppActivity.removeAppPausedResumedListener, - appPausedResumedListenerNative.get(), reinterpret_cast(this)); - #endif - } - - #if __ANDROID_API__ < 21 - // Dummy implementations for unsupported API levels. - void open (InternalOpenCameraResultCallback) {} - void takeStillPicture (std::function) {} - void startRecordingToFile (const File&, int) {} - void stopRecording() {} - - void addListener (CameraDevice::Listener*) {} - void removeListener (CameraDevice::Listener*) {} + auto* env = getEnv(); - String getCameraId() const noexcept { return {}; } - bool openedOk() const noexcept { return false; } - Time getTimeOfFirstRecordedFrame() const { return {}; } - static StringArray getAvailableDevices() - { - // Camera on Android requires API 21 or above. - jassertfalse; - return {}; + env->CallVoidMethod (getAppContext().get(), AndroidApplication.unregisterActivityLifecycleCallbacks, activityLifeListener.get()); + activityLifeListener.clear(); } - #else + JUCE_DECLARE_WEAK_REFERENCEABLE (Pimpl) String getCameraId() const noexcept { return cameraId; } @@ -530,15 +543,21 @@ struct CameraDevice::Pimpl void continueOpenRequest (bool granted) { - if (granted) + if (getAndroidSDKVersion() >= 21) { - getEnv()->CallVoidMethod (android.activity, JuceAppActivity.addAppPausedResumedListener, - appPausedResumedListenerNative.get(), reinterpret_cast (this)); - scopedCameraDevice.reset (new ScopedCameraDevice (*this, cameraId, cameraManager, handler, getAutoFocusModeToUse())); + if (granted) + { + getEnv()->CallVoidMethod (getAppContext().get(), AndroidApplication.registerActivityLifecycleCallbacks, activityLifeListener.get()); + scopedCameraDevice.reset (new ScopedCameraDevice (*this, cameraId, cameraManager, handler, getAutoFocusModeToUse())); + } + else + { + invokeCameraOpenCallback ("Camera permission not granted"); + } } else { - invokeCameraOpenCallback ("Camera permission not granted"); + invokeCameraOpenCallback ("Camera requires android sdk version 21 or greater"); } } @@ -546,7 +565,7 @@ struct CameraDevice::Pimpl void takeStillPicture (std::function pictureTakenCallbackToUse) { - if (pictureTakenCallbackToUse == nullptr) + if (pictureTakenCallbackToUse == nullptr || currentCaptureSessionMode == nullptr) { jassertfalse; return; @@ -611,6 +630,9 @@ struct CameraDevice::Pimpl static StringArray getAvailableDevices() { + if (getAndroidSDKVersion() < 21) + return StringArray(); // Camera requires SDK version 21 or later + StringArray results; auto* env = getEnv(); @@ -667,7 +689,7 @@ private: static LocalRef initialiseCameraManager() { - return LocalRef (getEnv()->CallObjectMethod (android.activity, JuceAppActivity.getSystemService, + return LocalRef (getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, javaString ("camera").get())); } @@ -692,9 +714,9 @@ private: JUCE_CAMERA_LOG ("Camera id: " + cameraId + ", characteristics keys num: " + String (size)); - for (int ikey = 0; ikey < size; ++ikey) + for (int i = 0; i < size; ++i) { - auto key = LocalRef (env->CallObjectMethod (keysList, JavaList.get, ikey)); + auto key = LocalRef (env->CallObjectMethod (keysList, JavaList.get, i)); auto jKeyName = LocalRef ((jstring) env->CallObjectMethod (key, CameraCharacteristicsKey.getName)); auto keyName = juceString (jKeyName); @@ -710,7 +732,7 @@ private: } else if (kvs.startsWith ("[Landroid.util.Range")) { - printRangeArrayElements (keyValue); + printRangeArrayElements (keyValue, keyName); } else { @@ -720,8 +742,8 @@ private: { JUCE_CAMERA_LOG ("Key: " + keyName); - for (int i = 0, j = 1; i < keyValueString.length(); i += chunkSize, ++j) - JUCE_CAMERA_LOG ("value part " + String (j) + ": " + keyValueString.substring (i, i + chunkSize)); + for (int j = 0, k = 1; j < keyValueString.length(); j += chunkSize, ++k) + JUCE_CAMERA_LOG ("value part " + String (k) + ": " + keyValueString.substring (j, k + chunkSize)); } else { @@ -771,7 +793,7 @@ private: JUCE_CAMERA_LOG ("Key: " + keyName + ", value: " + result); } - static void printRangeArrayElements (const LocalRef& rangeArray) + static void printRangeArrayElements (const LocalRef& rangeArray, const String& keyName) { auto* env = getEnv(); @@ -790,6 +812,7 @@ private: result << juceString (jRangeString) << " "; } + ignoreUnused (keyName); JUCE_CAMERA_LOG ("Key: " + keyName + ", value: " + result); } @@ -797,8 +820,8 @@ private: class StreamConfigurationMap { public: - StreamConfigurationMap (const GlobalRef& characteristics) - : scalerStreamConfigurationMap (getStreamConfigurationMap (characteristics)), + StreamConfigurationMap (const GlobalRef& cameraCharacteristicsToUse) + : scalerStreamConfigurationMap (getStreamConfigurationMap (cameraCharacteristicsToUse)), supportedPreviewOutputSizes (retrieveOutputSizes (scalerStreamConfigurationMap, getClassForName ("android.graphics.SurfaceTexture"), -1)), @@ -838,14 +861,14 @@ private: Array> supportedVideoRecordingOutputSizes; Rectangle defaultPreviewSize, previewBufferSize; - GlobalRef getStreamConfigurationMap (const GlobalRef& characteristics) + GlobalRef getStreamConfigurationMap (const GlobalRef& cameraCharacteristicsToUse) { auto* env = getEnv(); auto scalerStreamConfigurationMapKey = LocalRef (env->GetStaticObjectField (CameraCharacteristics, CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)); - return GlobalRef (LocalRef (env->CallObjectMethod (characteristics, + return GlobalRef (LocalRef (env->CallObjectMethod (cameraCharacteristicsToUse, CameraCharacteristics.get, scalerStreamConfigurationMapKey.get()))); } @@ -960,8 +983,8 @@ private: PreviewDisplay (Rectangle bufferSize) : textureViewSurfaceTextureListener (*this), - textureView (getEnv()->NewObject (AndroidTextureView, AndroidTextureView.constructor, - android.activity.get())), + textureView (LocalRef (getEnv()->NewObject (AndroidTextureView, AndroidTextureView.constructor, + getAppContext().get()))), bufferWidth (bufferSize.getWidth()), bufferHeight (bufferSize.getHeight()) { @@ -1036,7 +1059,7 @@ private: { auto* env = getEnv(); - auto windowManager = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getWindowManager)); + auto windowManager = LocalRef (env->CallObjectMethod (getAppContext(), AndroidContext.getSystemService, javaString ("window").get())); auto display = LocalRef (env->CallObjectMethod (windowManager, AndroidWindowManager.getDefaultDisplay)); auto rotation = env->CallIntMethod (display, AndroidDisplay.getRotation); @@ -1110,9 +1133,9 @@ private: int imageWidth, int imageHeight, int cameraSensorOrientationToUse) : owner (ownerToUse), cameraSensorOrientation (cameraSensorOrientationToUse), - imageReader (getEnv()->CallStaticObjectMethod (AndroidImageReader, AndroidImageReader.newInstance, - imageWidth, imageHeight, StreamConfigurationMap::jpegImageFormat, - numImagesToKeep)), + imageReader (LocalRef (getEnv()->CallStaticObjectMethod (AndroidImageReader, AndroidImageReader.newInstance, + imageWidth, imageHeight, StreamConfigurationMap::jpegImageFormat, + numImagesToKeep))), onImageAvailableListener (*this) { getEnv()->CallVoidMethod (imageReader, AndroidImageReader.setOnImageAvailableListener, @@ -1235,6 +1258,12 @@ private: AndroidBitmapFactory.decodeByteArray, byteArray.get(), (jint) 0, (jint) bufferSize)); + if (origBitmap == nullptr) + { + // Nothing to do, just get the bytes + return { byteArray, bufferSize }; + } + auto correctedBitmap = getBitmapWithCorrectOrientationFrom (origBitmap, rotationAngle); auto byteArrayOutputStream = LocalRef (env->NewObject (ByteArrayOutputStream, @@ -1518,7 +1547,7 @@ private: { auto* env = getEnv(); - auto windowManager = LocalRef (env->CallObjectMethod (android.activity, JuceAppActivity.getWindowManager)); + auto windowManager = LocalRef (env->CallObjectMethod (getAppContext(), AndroidContext.getSystemService, javaString ("window").get())); auto display = LocalRef (env->CallObjectMethod (windowManager, AndroidWindowManager.getDefaultDisplay)); auto rotation = env->CallIntMethod (display, AndroidDisplay.getRotation); @@ -1613,7 +1642,7 @@ private: env->CallVoidMethod (captureRequestBuilder, CaptureRequestBuilder.addTarget, surface.get()); } - previewCaptureRequest = GlobalRef (env->CallObjectMethod (captureRequestBuilder, CaptureRequestBuilder.build)); + previewCaptureRequest = GlobalRef (LocalRef(env->CallObjectMethod (captureRequestBuilder, CaptureRequestBuilder.build))); env->CallIntMethod (captureSession, CameraCaptureSession.setRepeatingRequest, previewCaptureRequest.get(), nullptr, handlerToUse.get()); @@ -1639,11 +1668,11 @@ private: env->CallVoidMethod (builder, CaptureRequestBuilder.addTarget, targetSurface); - setCaptureRequestBuilderIntegerKey (builder.get(), CaptureRequest.CONTROL_AF_MODE, autoFocusMode); + setCaptureRequestBuilderIntegerKey (builder, CaptureRequest.CONTROL_AF_MODE, autoFocusMode); auto stillPictureCaptureRequest = LocalRef (env->CallObjectMethod (builder, CaptureRequestBuilder.build)); - stillPictureTaker->takePicture (stillPictureCaptureRequest.get()); + stillPictureTaker->takePicture (stillPictureCaptureRequest); } private: @@ -1659,25 +1688,17 @@ private: previewCaptureRequest (previewCaptureRequestToUse), handler (handlerToUse), runnable (*this), - captureSessionPreviewCaptureCallback (LocalRef (getEnv()->NewObject (CameraCaptureSessionCaptureCallback, - CameraCaptureSessionCaptureCallback.constructor, - android.activity.get(), - reinterpret_cast (this), - true))), - captureSessionStillPictureCaptureCallback (LocalRef (getEnv()->NewObject (CameraCaptureSessionCaptureCallback, - CameraCaptureSessionCaptureCallback.constructor, - android.activity.get(), - reinterpret_cast (this), - false))), + captureSessionPreviewCaptureCallback (createCaptureSessionCallback (true)), + captureSessionStillPictureCaptureCallback (createCaptureSessionCallback (false)), autoFocusMode (autoFocusModeToUse) { } - void takePicture (jobject stillPictureCaptureRequestToUse) + void takePicture (const LocalRef& stillPictureCaptureRequestToUse) { JUCE_CAMERA_LOG ("Taking picture..."); - stillPictureCaptureRequest = GlobalRef (stillPictureCaptureRequestToUse); + stillPictureCaptureRequest = GlobalRef (LocalRef(stillPictureCaptureRequestToUse)); lockFocus(); } @@ -1698,6 +1719,29 @@ private: int autoFocusMode; + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(JZ)V") \ + CALLBACK (cameraCaptureSessionCaptureCompletedCallback, "cameraCaptureSessionCaptureCompleted", "(JZLandroid/hardware/camera2/CameraCaptureSession;Landroid/hardware/camera2/CaptureRequest;Landroid/hardware/camera2/TotalCaptureResult;)V") \ + CALLBACK (cameraCaptureSessionCaptureFailedCallback, "cameraCaptureSessionCaptureFailed", "(JZLandroid/hardware/camera2/CameraCaptureSession;Landroid/hardware/camera2/CaptureRequest;Landroid/hardware/camera2/CaptureFailure;)V") \ + CALLBACK (cameraCaptureSessionCaptureProgressedCallback, "cameraCaptureSessionCaptureProgressed", "(JZLandroid/hardware/camera2/CameraCaptureSession;Landroid/hardware/camera2/CaptureRequest;Landroid/hardware/camera2/CaptureResult;)V") \ + CALLBACK (cameraCaptureSessionCaptureStartedCallback, "cameraCaptureSessionCaptureStarted", "(JZLandroid/hardware/camera2/CameraCaptureSession;Landroid/hardware/camera2/CaptureRequest;JJ)V") \ + CALLBACK (cameraCaptureSessionCaptureSequenceAbortedCallback, "cameraCaptureSessionCaptureSequenceAborted", "(JZLandroid/hardware/camera2/CameraCaptureSession;I)V") \ + CALLBACK (cameraCaptureSessionCaptureSequenceCompletedCallback, "cameraCaptureSessionCaptureSequenceCompleted", "(JZLandroid/hardware/camera2/CameraCaptureSession;IJ)V") + + DECLARE_JNI_CLASS_WITH_BYTECODE (CameraCaptureSessionCaptureCallback, "com/roli/juce/CameraCaptureSessionCaptureCallback", 21, CameraSupportByteCode, sizeof(CameraSupportByteCode)) + #undef JNI_CLASS_MEMBERS + + LocalRef createCaptureSessionCallback (bool createPreviewSession) + { + return LocalRef(getEnv()->NewObject (CameraCaptureSessionCaptureCallback, + CameraCaptureSessionCaptureCallback.constructor, + reinterpret_cast (this), + createPreviewSession ? 1 : 0)); + } + + //============================================================================== + enum class State { idle = 0, @@ -1863,7 +1907,7 @@ private: // Delay still picture capture for devices that can't handle it right after // stopRepeating/abortCaptures calls. if (delayedCaptureRunnable.get() == nullptr) - delayedCaptureRunnable = GlobalRef (CreateJavaInterface (&runnable, "java/lang/Runnable").get()); + delayedCaptureRunnable = GlobalRef (CreateJavaInterface (&runnable, "java/lang/Runnable")); env->CallBooleanMethod (handler, AndroidHandler.postDelayed, delayedCaptureRunnable.get(), (jlong) 200); } @@ -1982,12 +2026,73 @@ private: ignoreUnused (isPreview, session, request, timestamp, frameNumber); } - friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int); - friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64); - friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64); + //============================================================================== + static void cameraCaptureSessionCaptureCompletedCallback (JNIEnv*, jobject /*object*/, jlong host, jboolean isPreview, jobject rawSession, jobject rawRequest, jobject rawResult) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + LocalRef request (getEnv()->NewLocalRef(rawRequest)); + LocalRef result (getEnv()->NewLocalRef(rawResult)); + + myself->cameraCaptureSessionCaptureCompleted (isPreview != 0, session, request, result); + } + } + + static void cameraCaptureSessionCaptureFailedCallback (JNIEnv*, jobject /*object*/, jlong host, jboolean isPreview, jobject rawSession, jobject rawRequest, jobject rawResult) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + LocalRef request (getEnv()->NewLocalRef(rawRequest)); + LocalRef result (getEnv()->NewLocalRef(rawResult)); + + myself->cameraCaptureSessionCaptureFailed (isPreview != 0, session, request, result); + } + } + + static void cameraCaptureSessionCaptureProgressedCallback (JNIEnv*, jobject /*object*/, jlong host, jboolean isPreview, jobject rawSession, jobject rawRequest, jobject rawResult) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + LocalRef request (getEnv()->NewLocalRef(rawRequest)); + LocalRef result (getEnv()->NewLocalRef(rawResult)); + + myself->cameraCaptureSessionCaptureProgressed (isPreview != 0, session, request, result); + } + } + + static void cameraCaptureSessionCaptureSequenceAbortedCallback (JNIEnv*, jobject /*object*/, jlong host, jboolean isPreview, jobject rawSession, jint sequenceId) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + + myself->cameraCaptureSessionCaptureSequenceAborted (isPreview != 0, session, sequenceId); + } + } + + static void cameraCaptureSessionCaptureSequenceCompletedCallback (JNIEnv*, jobject /*object*/, jlong host, jboolean isPreview, jobject rawSession, jint sequenceId, jlong frameNumber) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + + myself->cameraCaptureSessionCaptureSequenceCompleted (isPreview != 0, session, sequenceId, frameNumber); + } + } + + static void cameraCaptureSessionCaptureStartedCallback (JNIEnv*, jobject /*object*/, jlong host, jboolean isPreview, jobject rawSession, jobject rawRequest, jlong timestamp, jlong frameNumber) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + LocalRef request (getEnv()->NewLocalRef(rawRequest)); + + myself->cameraCaptureSessionCaptureStarted (isPreview != 0, session, request, timestamp, frameNumber); + } + } }; //============================================================================== @@ -2012,6 +2117,19 @@ private: JUCE_DECLARE_WEAK_REFERENCEABLE (CaptureSession) + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + CALLBACK(cameraCaptureSessionActiveCallback, "cameraCaptureSessionActive", "(JLandroid/hardware/camera2/CameraCaptureSession;)V") \ + CALLBACK(cameraCaptureSessionClosedCallback, "cameraCaptureSessionClosed", "(JLandroid/hardware/camera2/CameraCaptureSession;)V") \ + CALLBACK(cameraCaptureSessionConfigureFailedCallback, "cameraCaptureSessionConfigureFailed", "(JLandroid/hardware/camera2/CameraCaptureSession;)V") \ + CALLBACK(cameraCaptureSessionConfiguredCallback, "cameraCaptureSessionConfigured", "(JLandroid/hardware/camera2/CameraCaptureSession;)V") \ + CALLBACK(cameraCaptureSessionReadyCallback, "cameraCaptureSessionReady", "(JLandroid/hardware/camera2/CameraCaptureSession;)V") + + DECLARE_JNI_CLASS_WITH_MIN_SDK (CameraCaptureSessionStateCallback, "com/roli/juce/CameraCaptureSessionStateCallback", 21) + #undef JNI_CLASS_MEMBERS + + //============================================================================== CaptureSession (ScopedCameraDevice& scopedCameraDeviceToUse, ConfiguredCallback& configuredCallbackToUse, const LocalRef& surfacesList, GlobalRef& handlerToUse, @@ -2024,7 +2142,6 @@ private: (jint) captureSessionTemplate))), captureSessionStateCallback (LocalRef (getEnv()->NewObject (CameraCaptureSessionStateCallback, CameraCaptureSessionStateCallback.constructor, - android.activity.get(), reinterpret_cast (this)))), autoFocusMode (autoFocusModeToUse) { @@ -2079,7 +2196,7 @@ private: }); } - void cameraCaptureSessionConfigured (jobject session) + void cameraCaptureSessionConfigured (const LocalRef& session) { JUCE_CAMERA_LOG ("cameraCaptureSessionConfigured()"); @@ -2117,26 +2234,65 @@ private: }); } - void cameraCaptureSessionReady (jobject session) + void cameraCaptureSessionReady (const LocalRef& session) { JUCE_CAMERA_LOG ("cameraCaptureSessionReady()"); ignoreUnused (session); } - friend class ScopedCameraDevice; + //============================================================================== + static void cameraCaptureSessionActiveCallback (JNIEnv*, jobject, jlong host, jobject rawSession) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); - friend void juce_cameraCaptureSessionActive (int64, void*); - friend void juce_cameraCaptureSessionClosed (int64, void*); - friend void juce_cameraCaptureSessionConfigureFailed (int64, void*); - friend void juce_cameraCaptureSessionConfigured (int64, void*); - friend void juce_cameraCaptureSessionReady (int64, void*); + myself->cameraCaptureSessionActive (session); + } + } + + static void cameraCaptureSessionClosedCallback (JNIEnv*, jobject, jlong host, jobject rawSession) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + + myself->cameraCaptureSessionClosed (session); + } + } + + static void cameraCaptureSessionConfigureFailedCallback (JNIEnv*, jobject, jlong host, jobject rawSession) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + + myself->cameraCaptureSessionConfigureFailed (session); + } + } - friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int); - friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64); - friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64); + static void cameraCaptureSessionConfiguredCallback (JNIEnv*, jobject, jlong host, jobject rawSession) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + + myself->cameraCaptureSessionConfigured (session); + } + } + + static void cameraCaptureSessionReadyCallback (JNIEnv*, jobject, jlong host, jobject rawSession) + { + if (auto* myself = reinterpret_cast (host)) + { + LocalRef session (getEnv()->NewLocalRef(rawSession)); + + myself->cameraCaptureSessionReady (session); + } + } + + //============================================================================== + friend class ScopedCameraDevice; JUCE_DECLARE_NON_COPYABLE (CaptureSession) }; @@ -2148,10 +2304,7 @@ private: cameraId (cameraIdToUse), cameraManager (cameraManagerToUse), handler (handlerToUse), - cameraStateCallback (LocalRef (getEnv()->NewObject (CameraDeviceStateCallback, - CameraDeviceStateCallback.constructor, - android.activity.get(), - reinterpret_cast (this)))), + cameraStateCallback (createCameraStateCallbackObject()), autoFocusMode (autoFocusModeToUse) { open(); @@ -2230,6 +2383,25 @@ private: WaitableEvent closedEvent; + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + CALLBACK (cameraDeviceStateClosedCallback, "cameraDeviceStateClosed", "(JLandroid/hardware/camera2/CameraDevice;)V") \ + CALLBACK (cameraDeviceStateDisconnectedCallback, "cameraDeviceStateDisconnected", "(JLandroid/hardware/camera2/CameraDevice;)V") \ + CALLBACK (cameraDeviceStateErrorCallback, "cameraDeviceStateError", "(JLandroid/hardware/camera2/CameraDevice;I)V") \ + CALLBACK (cameraDeviceStateOpenedCallback, "cameraDeviceStateOpened", "(JLandroid/hardware/camera2/CameraDevice;)V") + + DECLARE_JNI_CLASS_WITH_MIN_SDK (CameraDeviceStateCallback, "com/roli/juce/CameraDeviceStateCallback", 21) + #undef JNI_CLASS_MEMBERS + + LocalRef createCameraStateCallbackObject() + { + return LocalRef (getEnv()->NewObject (CameraDeviceStateCallback, + CameraDeviceStateCallback.constructor, + reinterpret_cast (this))); + } + + //============================================================================== void cameraDeviceStateClosed() { JUCE_CAMERA_LOG ("cameraDeviceStateClosed()"); @@ -2273,7 +2445,7 @@ private: }); } - void cameraDeviceStateOpened (jobject cameraDeviceToUse) + void cameraDeviceStateOpened (const LocalRef& cameraDeviceToUse) { JUCE_CAMERA_LOG ("cameraDeviceStateOpened()"); @@ -2289,23 +2461,34 @@ private: MessageManager::callAsync ([this]() { owner.cameraOpenFinished (openError); }); } - friend void juce_cameraDeviceStateClosed (int64); - friend void juce_cameraDeviceStateDisconnected (int64); - friend void juce_cameraDeviceStateError (int64, int); - friend void juce_cameraDeviceStateOpened (int64, void*); + //============================================================================== + static void JNICALL cameraDeviceStateClosedCallback (JNIEnv*, jobject, jlong host, jobject) + { + if (auto* myself = reinterpret_cast(host)) + myself->cameraDeviceStateClosed(); + } + + static void JNICALL cameraDeviceStateDisconnectedCallback (JNIEnv*, jobject, jlong host, jobject) + { + if (auto* myself = reinterpret_cast(host)) + myself->cameraDeviceStateDisconnected(); + } + + static void JNICALL cameraDeviceStateErrorCallback (JNIEnv*, jobject, jlong host, jobject, jint error) + { + if (auto* myself = reinterpret_cast(host)) + myself->cameraDeviceStateError (error); + } - friend void juce_cameraCaptureSessionActive (int64, void*); - friend void juce_cameraCaptureSessionClosed (int64, void*); - friend void juce_cameraCaptureSessionConfigureFailed (int64, void*); - friend void juce_cameraCaptureSessionConfigured (int64, void*); - friend void juce_cameraCaptureSessionReady (int64, void*); + static void JNICALL cameraDeviceStateOpenedCallback (JNIEnv*, jobject, jlong host, jobject rawCamera) + { + if (auto* myself = reinterpret_cast(host)) + { + LocalRef camera(getEnv()->NewLocalRef(rawCamera)); - friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int); - friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64); - friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64); + myself->cameraDeviceStateOpened (camera); + } + } }; //============================================================================== @@ -2597,12 +2780,7 @@ private: public: DeviceOrientationChangeListener (PreviewDisplay& pd) : previewDisplay (pd), - orientationEventListener (getEnv()->NewObject (OrientationEventListener, - OrientationEventListener.constructor, - android.activity.get(), - reinterpret_cast (this), - android.activity.get(), - sensorDelayUI)), + orientationEventListener (createOrientationEventListener()), canDetectChange (getEnv()->CallBooleanMethod (orientationEventListener, OrientationEventListener.canDetectOrientation) != 0), deviceOrientation (Desktop::getInstance().getCurrentOrientation()), @@ -2650,6 +2828,27 @@ private: Desktop::DisplayOrientation lastKnownScreenOrientation; int numChecksForOrientationChange = 10; + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (canDetectOrientation, "canDetectOrientation", "()Z") \ + METHOD (constructor, "", "(JLandroid/content/Context;I)V") \ + METHOD (disable, "disable", "()V") \ + METHOD (enable, "enable", "()V") \ + CALLBACK (deviceOrientationChanged, "deviceOrientationChanged", "(JI)V") + + DECLARE_JNI_CLASS_WITH_MIN_SDK (OrientationEventListener, "com/roli/juce/JuceOrientationEventListener", 21) + #undef JNI_CLASS_MEMBERS + + LocalRef createOrientationEventListener() + { + return LocalRef (getEnv()->NewObject (OrientationEventListener, + OrientationEventListener.constructor, + reinterpret_cast (this), + getAppContext().get(), + sensorDelayUI)); + } + + //============================================================================== void orientationChanged (int orientation) { jassert (orientation < 360); @@ -2702,7 +2901,11 @@ private: } } - friend void juce_deviceOrientationChanged (int64, int); + static void deviceOrientationChanged (JNIEnv*, jobject /*obj*/, jlong host, jint orientation) + { + if (auto* myself = reinterpret_cast (host)) + myself->orientationChanged (orientation); + } }; //============================================================================== @@ -2712,9 +2915,7 @@ private: String cameraId; InternalOpenCameraResultCallback cameraOpenCallback; - #if __ANDROID_API__ >= 21 - AppPausedResumedListener appPausedResumedListener; - GlobalRef appPausedResumedListenerNative; + GlobalRef activityLifeListener; GlobalRef cameraManager; GlobalRef cameraCharacteristics; @@ -2738,7 +2939,6 @@ private: Time firstRecordedFrameTimeMs; bool notifiedOfCameraOpening = false; - #endif bool appWasPaused = false; @@ -2948,7 +3148,7 @@ private: } //============================================================================== - void appPaused() override + void onActivityPaused (jobject) override { JUCE_CAMERA_LOG ("appPaused, closing camera..."); @@ -2965,7 +3165,7 @@ private: stopBackgroundThread(); } - void appResumed() override + void onActivityResumed (jobject) override { // Only care about resumed event when paused event was called first. if (! appWasPaused) @@ -2999,7 +3199,12 @@ private: { auto* env = getEnv(); - env->CallBooleanMethod (handlerThread, AndroidHandlerThread.quitSafely); + auto quitSafelyMethod = env->GetMethodID(AndroidHandlerThread, "quitSafely", "()Z"); + + // this code will only run on SDK >= 21 + jassert(quitSafelyMethod != nullptr); + + env->CallBooleanMethod (handlerThread, quitSafelyMethod); env->CallVoidMethod (handlerThread, AndroidHandlerThread.join); jniCheckHasExceptionOccurredAndClear(); @@ -3007,30 +3212,9 @@ private: handlerThread.clear(); handler.clear(); } -#endif friend struct CameraDevice::ViewerComponent; - friend void juce_cameraDeviceStateClosed (int64); - friend void juce_cameraDeviceStateDisconnected (int64); - friend void juce_cameraDeviceStateError (int64, int); - friend void juce_cameraDeviceStateOpened (int64, void*); - - friend void juce_cameraCaptureSessionActive (int64, void*); - friend void juce_cameraCaptureSessionClosed (int64, void*); - friend void juce_cameraCaptureSessionConfigureFailed (int64, void*); - friend void juce_cameraCaptureSessionConfigured (int64, void*); - friend void juce_cameraCaptureSessionReady (int64, void*); - - friend void juce_cameraCaptureSessionCaptureCompleted (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureFailed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureProgressed (int64, bool, void*, void*, void*); - friend void juce_cameraCaptureSessionCaptureSequenceAborted (int64, bool, void*, int); - friend void juce_cameraCaptureSessionCaptureSequenceCompleted (int64, bool, void*, int, int64); - friend void juce_cameraCaptureSessionCaptureStarted (int64, bool, void*, void*, int64, int64); - - friend void juce_deviceOrientationChanged (int64, int); - JUCE_DECLARE_NON_COPYABLE (Pimpl) }; @@ -3040,7 +3224,6 @@ struct CameraDevice::ViewerComponent : public Component, { ViewerComponent (CameraDevice& device) : ComponentMovementWatcher (this) { - #if __ANDROID_API__ >= 21 auto previewSize = device.pimpl->streamConfigurationMap.getDefaultPreviewSize(); targetAspectRatio = previewSize.getWidth() / (float) previewSize.getHeight(); @@ -3052,9 +3235,6 @@ struct CameraDevice::ViewerComponent : public Component, addAndMakeVisible (viewerComponent); viewerComponent.setView (device.pimpl->previewDisplay.getNativeView()); - #else - ignoreUnused (device); - #endif } private: @@ -3108,222 +3288,8 @@ String CameraDevice::getFileExtension() return ".mp4"; } -#if __ANDROID_API__ >= 21 -//============================================================================== -void juce_cameraDeviceStateClosed (int64 host) -{ - reinterpret_cast (host)->cameraDeviceStateClosed(); -} - -void juce_cameraDeviceStateDisconnected (int64 host) -{ - reinterpret_cast (host)->cameraDeviceStateDisconnected(); -} - -void juce_cameraDeviceStateError (int64 host, int error) -{ - reinterpret_cast (host)->cameraDeviceStateError (error); -} - -void juce_cameraDeviceStateOpened (int64 host, void* camera) -{ - reinterpret_cast (host)->cameraDeviceStateOpened ((jobject) camera); -} - -//============================================================================== -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraDeviceStateCallback), cameraDeviceStateClosed, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*camera*/)) -{ - setEnv (env); - - juce_cameraDeviceStateClosed (host); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraDeviceStateCallback), cameraDeviceStateDisconnected, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*camera*/)) -{ - setEnv (env); - - juce_cameraDeviceStateDisconnected (host); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraDeviceStateCallback), cameraDeviceStateError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*camera*/, int error)) -{ - setEnv (env); - - juce_cameraDeviceStateError (host, error); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraDeviceStateCallback), cameraDeviceStateOpened, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject camera)) -{ - setEnv (env); - - juce_cameraDeviceStateOpened (host, camera); -} - //============================================================================== -void juce_cameraCaptureSessionActive (int64 host, void* session) -{ - auto* juceCaptureSession = reinterpret_cast (host); - juceCaptureSession->cameraCaptureSessionActive ((jobject) session); -} - -void juce_cameraCaptureSessionClosed (int64 host, void* session) -{ - auto* juceCaptureSession = reinterpret_cast (host); - juceCaptureSession->cameraCaptureSessionClosed ((jobject) session); -} - -void juce_cameraCaptureSessionConfigureFailed (int64 host, void* session) -{ - auto* juceCaptureSession = reinterpret_cast (host); - juceCaptureSession->cameraCaptureSessionConfigureFailed ((jobject) session); -} - -void juce_cameraCaptureSessionConfigured (int64 host, void* session) -{ - auto* juceCaptureSession = reinterpret_cast (host); - juceCaptureSession->cameraCaptureSessionConfigured ((jobject) session); -} - -void juce_cameraCaptureSessionReady (int64 host, void* session) -{ - auto* juceCaptureSession = reinterpret_cast (host); - juceCaptureSession->cameraCaptureSessionReady ((jobject) session); -} - -//============================================================================== -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionStateCallback), cameraCaptureSessionActive, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject session)) -{ - setEnv (env); - - juce_cameraCaptureSessionActive (host, session); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionStateCallback), cameraCaptureSessionClosed, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject session)) -{ - setEnv (env); - - juce_cameraCaptureSessionClosed (host, session); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionStateCallback), cameraCaptureSessionConfigureFailed, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject session)) -{ - setEnv (env); - - juce_cameraCaptureSessionConfigureFailed (host, session); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionStateCallback), cameraCaptureSessionConfigured, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject session)) -{ - setEnv (env); - - juce_cameraCaptureSessionConfigured (host, session); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionStateCallback), cameraCaptureSessionReady, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject session)) -{ - setEnv (env); - - juce_cameraCaptureSessionReady (host, session); -} - - -//============================================================================== -void juce_cameraCaptureSessionCaptureCompleted (int64 host, bool isPreview, void* session, void* request, void* result) -{ - auto* stillPictureTaker = reinterpret_cast (host); - stillPictureTaker->cameraCaptureSessionCaptureCompleted (isPreview, (jobject) session, (jobject) request, (jobject) result); -} - -void juce_cameraCaptureSessionCaptureFailed (int64 host, bool isPreview, void* session, void* request, void* failure) -{ - auto* stillPictureTaker = reinterpret_cast (host); - stillPictureTaker->cameraCaptureSessionCaptureFailed (isPreview, (jobject) session, (jobject) request, (jobject) failure); -} - -void juce_cameraCaptureSessionCaptureProgressed (int64 host, bool isPreview, void* session, void* request, void* partialResult) -{ - auto* stillPictureTaker = reinterpret_cast (host); - stillPictureTaker->cameraCaptureSessionCaptureProgressed (isPreview, (jobject) session, (jobject) request, (jobject) partialResult); -} - -void juce_cameraCaptureSessionCaptureSequenceAborted (int64 host, bool isPreview, void* session, int sequenceId) -{ - auto* stillPictureTaker = reinterpret_cast (host); - stillPictureTaker->cameraCaptureSessionCaptureSequenceAborted (isPreview, (jobject) session, sequenceId); -} - -void juce_cameraCaptureSessionCaptureSequenceCompleted (int64 host, bool isPreview, void* session, int sequenceId, int64 frameNumber) -{ - auto* stillPictureTaker = reinterpret_cast (host); - stillPictureTaker->cameraCaptureSessionCaptureSequenceCompleted (isPreview, (jobject) session, sequenceId, frameNumber); -} - -void juce_cameraCaptureSessionCaptureStarted (int64 host, bool isPreview, void* session, void* request, int64 timestamp, int64 frameNumber) -{ - auto* stillPictureTaker = reinterpret_cast (host); - stillPictureTaker->cameraCaptureSessionCaptureStarted (isPreview, (jobject) session, (jobject) request, timestamp, frameNumber); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionCaptureCallback), cameraCaptureSessionCaptureCompleted, \ - void, (JNIEnv* env, jobject /*activity*/, jlong host, bool isPreview, jobject session, jobject request, jobject result)) -{ - setEnv (env); - - juce_cameraCaptureSessionCaptureCompleted (host, isPreview, session, request, result); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionCaptureCallback), cameraCaptureSessionCaptureFailed, \ - void, (JNIEnv* env, jobject /*activity*/, jlong host, bool isPreview, jobject session, jobject request, jobject failure)) -{ - setEnv (env); - - juce_cameraCaptureSessionCaptureFailed (host, isPreview, session, request, failure); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionCaptureCallback), cameraCaptureSessionCaptureProgressed, \ - void, (JNIEnv* env, jobject /*activity*/, jlong host, bool isPreview, jobject session, jobject request, jobject partialResult)) -{ - setEnv (env); - - juce_cameraCaptureSessionCaptureProgressed (host, isPreview, session, request, partialResult); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionCaptureCallback), cameraCaptureSessionCaptureSequenceAborted, \ - void, (JNIEnv* env, jobject /*activity*/, jlong host, bool isPreview, jobject session, jint sequenceId)) -{ - setEnv (env); - - juce_cameraCaptureSessionCaptureSequenceAborted (host, isPreview, session, (int) sequenceId); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionCaptureCallback), cameraCaptureSessionCaptureSequenceCompleted, \ - void, (JNIEnv* env, jobject /*activity*/, jlong host, bool isPreview, jobject session, jint sequenceId, jlong frameNumber)) -{ - setEnv (env); - - juce_cameraCaptureSessionCaptureSequenceCompleted (host, isPreview, session, (int) sequenceId, frameNumber); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024CameraCaptureSessionCaptureCallback), cameraCaptureSessionCaptureStarted, \ - void, (JNIEnv* env, jobject /*activity*/, jlong host, bool isPreview, jobject session, jobject request, int64 timestamp, int64 frameNumber)) -{ - setEnv (env); - - juce_cameraCaptureSessionCaptureStarted (host, isPreview, session, request, timestamp, frameNumber); -} - -//============================================================================== -void juce_deviceOrientationChanged (int64 host, int orientation) -{ - auto* listener = reinterpret_cast (host); - listener->orientationChanged (orientation); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceOrientationEventListener), deviceOrientationChanged, \ - void, (JNIEnv* env, jobject /*activity*/, jlong host, jint orientation)) -{ - setEnv (env); - - juce_deviceOrientationChanged (host, (int) orientation); -} -#endif +CameraDevice::Pimpl::ScopedCameraDevice::CaptureSession::StillPictureTaker::CameraCaptureSessionCaptureCallback_Class CameraDevice::Pimpl::ScopedCameraDevice::CaptureSession::StillPictureTaker::CameraCaptureSessionCaptureCallback; +CameraDevice::Pimpl::ScopedCameraDevice::CameraDeviceStateCallback_Class CameraDevice::Pimpl::ScopedCameraDevice::CameraDeviceStateCallback; +CameraDevice::Pimpl::ScopedCameraDevice::CaptureSession::CameraCaptureSessionStateCallback_Class CameraDevice::Pimpl::ScopedCameraDevice::CaptureSession::CameraCaptureSessionStateCallback; +CameraDevice::Pimpl::DeviceOrientationChangeListener::OrientationEventListener_Class CameraDevice::Pimpl::DeviceOrientationChangeListener::OrientationEventListener; diff --git a/modules/juce_video/native/juce_android_Video.h b/modules/juce_video/native/juce_android_Video.h index e691fb5cfd..7b91f20615 100644 --- a/modules/juce_video/native/juce_android_Video.h +++ b/modules/juce_video/native/juce_android_Video.h @@ -22,8 +22,81 @@ ============================================================================== */ -#if __ANDROID_API__ >= 21 -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +//============================================================================== +// This byte-code is generated from: +// +// native/java/com/roli/juce/MediaControllerCallback.java +// native/java/com/roli/juce/MediaSessionCallback.java +// native/java/com/roli/juce/SystemVolumeObserver.java +// +// files with min sdk version 21 +// See juce_core/native/java/README.txt on how to generate this byte-code. +static const unsigned char MediaSessionByteCode[] = +{31,139,8,8,235,44,227,91,0,3,77,101,100,105,97,83,101,115,115,105,111,110,46,100,101,120,0,149,152,127,108,28,71,21,199, +223,236,253,244,157,125,183,119,254,21,55,110,226,52,206,15,151,218,119,36,129,58,57,215,248,7,54,113,56,59,110,206,118, +131,41,13,107,223,214,222,228,188,123,236,238,185,177,80,69,85,165,82,145,250,7,34,32,85,72,69,65,2,137,72,1,84,250,15, +63,42,26,9,81,33,129,10,149,42,17,4,130,34,1,165,168,18,63,148,72,21,164,130,239,204,206,158,247,46,14,42,151,124,246, +189,121,239,205,236,155,55,51,231,219,45,235,23,19,249,163,31,162,82,113,228,219,95,188,252,253,171,127,206,39,23,143, +254,97,228,234,196,107,173,223,121,242,213,183,126,120,41,67,84,37,162,139,75,199,178,36,63,215,85,162,85,242,236,221, +224,54,243,228,89,133,40,2,89,9,17,29,134,252,18,36,254,211,27,97,162,249,86,162,99,112,62,22,39,210,64,25,84,128,13,62, +7,158,6,207,128,23,192,117,240,27,112,11,244,180,16,13,131,243,224,10,248,5,248,15,56,152,32,58,14,230,64,25,60,11,94,4, +191,2,239,129,222,36,81,30,140,131,18,248,50,120,9,188,6,222,6,239,128,191,131,91,224,95,128,144,95,24,180,128,20,232,0, +187,192,189,160,15,244,131,33,240,16,208,193,6,184,4,158,7,95,5,87,193,183,192,75,224,85,240,22,136,181,17,61,0,102,193, +18,48,192,103,193,243,224,26,248,17,248,61,120,27,188,7,98,41,162,46,112,63,120,8,76,131,57,160,129,45,240,121,112,25, +188,0,94,4,175,128,215,193,31,193,59,224,31,224,93,144,73,35,111,48,0,78,128,105,48,11,74,224,83,224,34,120,50,237,173, +85,12,160,204,132,146,18,202,70,178,20,132,116,8,67,17,194,8,91,128,248,226,183,131,14,208,9,186,228,218,239,2,61,224,30, +208,11,246,129,7,64,20,40,114,191,112,61,20,208,219,165,190,79,142,197,63,247,73,253,58,18,218,47,245,159,66,239,151,250, +47,3,250,141,128,254,38,244,3,82,255,11,244,67,82,191,9,253,160,212,111,7,244,112,124,91,111,13,232,29,208,7,164,222,27, +176,31,142,123,123,153,235,249,128,125,24,250,253,82,31,131,254,1,169,159,132,62,40,235,51,31,208,207,198,121,61,147,20, +150,53,61,6,84,41,51,196,104,72,214,153,183,25,254,29,17,245,75,211,89,33,147,244,105,89,195,163,196,215,77,21,181,141, +193,162,136,181,243,100,18,215,115,98,252,176,104,167,17,113,92,200,40,157,16,50,78,5,233,31,17,178,141,166,132,108,165, +25,33,83,52,43,100,150,230,132,12,209,195,34,79,111,60,46,71,165,252,136,144,45,52,38,100,132,198,165,125,66,200,16,125, +76,200,78,58,41,219,167,164,252,184,144,25,42,202,246,105,217,111,94,182,207,72,89,146,246,5,217,94,20,245,73,136,60,50, +176,79,10,217,65,211,66,182,211,178,216,87,93,244,168,172,47,35,111,111,243,207,30,240,77,52,6,84,175,173,74,127,139,244, +239,149,242,19,210,159,145,254,136,180,247,73,249,13,233,231,190,30,232,97,228,197,245,75,105,111,95,87,213,8,218,203,89, +134,58,242,138,241,181,126,78,250,74,125,140,170,99,45,164,124,112,219,119,185,193,151,104,240,125,165,193,151,20,62,69, +158,168,175,165,189,28,82,98,79,48,177,47,174,5,227,243,173,164,176,84,61,191,239,214,243,139,138,252,20,244,100,194,67, +244,131,134,177,188,241,95,73,123,103,55,171,166,235,227,255,164,62,190,130,241,219,197,248,190,239,103,13,190,14,225, +139,192,195,235,247,122,208,183,208,41,230,225,223,251,215,129,123,71,101,252,239,252,248,60,226,23,187,40,52,190,29,255, +167,64,188,111,251,107,67,254,222,183,204,223,118,168,207,173,134,28,187,69,142,81,185,175,255,157,246,246,66,150,170, +125,220,242,201,253,33,90,238,15,139,254,17,57,106,68,245,190,3,77,53,134,94,173,116,15,203,147,217,119,8,123,41,65,195, +216,105,37,244,172,230,251,229,220,189,53,76,169,222,119,220,112,184,157,22,242,33,244,229,217,181,66,134,132,92,65,146, +217,172,57,198,72,61,146,218,49,194,68,182,121,58,252,79,170,127,24,109,239,97,254,247,212,111,43,13,50,42,101,92,202, +132,172,76,187,248,110,87,228,56,254,30,103,228,213,134,145,247,183,128,145,247,183,128,247,73,226,76,249,49,109,178,47, +31,133,183,59,164,189,75,218,187,16,205,219,97,105,111,39,118,152,216,8,69,71,12,211,112,71,41,60,90,24,88,162,228,228, +233,185,133,169,185,133,115,139,103,102,136,157,34,86,164,174,162,102,150,109,203,40,231,180,106,53,55,190,234,26,155, +134,187,85,160,125,117,251,170,101,186,186,233,230,38,61,121,70,119,172,202,166,110,23,168,103,231,144,139,110,129,118, +221,225,154,17,162,64,247,213,61,101,205,213,86,52,71,247,7,62,189,226,232,182,24,248,222,122,204,134,94,54,180,220,44, +191,206,234,174,198,187,20,40,223,228,118,116,199,49,44,211,11,227,131,217,86,165,162,219,253,147,90,165,178,162,173,94, +40,208,177,247,217,99,190,162,109,241,30,51,230,227,86,129,6,255,87,175,146,215,8,220,100,232,253,132,63,92,211,107,250, +140,171,111,20,232,192,93,226,253,36,74,174,230,234,5,202,212,195,76,221,205,45,218,70,129,218,235,38,203,201,77,212,204, +114,5,113,29,65,227,73,141,27,237,96,185,171,182,181,105,148,117,59,87,210,93,215,48,215,156,254,210,150,35,18,57,84,92, +181,54,114,40,129,145,59,95,91,213,155,171,178,61,195,254,157,2,229,204,238,30,229,221,102,201,170,212,54,244,224,26,151, +181,202,166,113,33,167,153,166,133,153,242,153,151,140,53,83,115,107,54,102,147,45,158,215,54,181,92,69,51,215,114,37, +215,70,190,5,74,123,182,154,107,84,114,69,195,113,73,109,50,20,104,172,201,50,242,255,173,201,104,129,122,239,50,251,33, +62,48,245,236,52,101,233,218,105,158,158,139,45,145,178,116,138,66,75,167,196,165,72,97,92,138,176,21,209,44,114,91,145, +219,138,203,220,182,140,38,20,182,76,10,68,92,147,39,146,218,124,109,193,90,116,116,58,40,15,214,137,28,38,229,45,104, +206,17,25,228,54,69,10,231,54,106,142,177,74,81,156,71,91,115,168,115,77,119,199,171,213,138,177,42,106,45,79,42,101,97, +110,58,216,20,94,183,80,220,22,126,245,238,165,24,101,10,27,56,19,148,17,101,156,168,185,174,101,122,39,154,246,110,52, +22,108,188,86,54,44,126,128,38,215,177,120,122,153,246,52,5,248,7,217,247,239,111,242,55,236,127,63,168,249,46,114,9,62, +170,59,48,108,33,32,38,2,102,202,50,69,233,159,215,106,200,95,109,48,97,120,218,221,108,153,182,173,141,89,57,66,54,232, +44,233,250,133,5,171,113,136,146,107,85,101,70,190,37,176,246,126,202,241,13,57,81,202,90,230,29,85,137,99,13,132,74,109, +150,57,173,57,238,180,101,63,161,217,101,30,60,187,93,228,169,77,94,227,12,183,53,86,45,230,79,46,42,167,148,185,115,34, +93,158,233,142,98,166,44,83,236,249,64,46,103,244,39,12,83,104,114,194,89,174,53,213,184,21,182,11,70,117,193,154,227,91, +71,245,91,243,182,190,105,88,53,135,167,224,89,234,39,138,103,39,170,21,170,90,14,69,62,195,237,212,109,235,107,56,155, +56,91,141,95,252,148,112,244,202,227,178,42,208,221,41,83,91,169,224,190,105,103,221,170,85,202,19,186,111,72,56,174,102, +187,206,35,134,187,78,17,135,79,141,194,238,186,225,80,220,181,188,111,11,234,169,153,119,187,77,168,102,27,20,217,212, +42,200,37,203,62,28,75,141,12,82,15,27,142,165,150,105,47,155,226,226,0,123,140,139,1,70,97,118,182,115,15,30,14,135,136, +27,58,100,244,131,252,121,113,132,29,199,117,183,112,236,19,215,67,202,232,17,46,199,184,123,144,61,194,245,73,174,79, +243,203,12,123,16,215,163,34,50,165,228,49,82,113,112,144,246,42,203,43,177,212,143,151,169,139,61,26,75,13,50,53,211,75, +74,150,173,244,70,186,169,123,119,55,235,86,187,241,59,42,18,97,74,252,169,167,194,47,39,216,211,10,5,105,99,55,18,140, +221,4,95,72,50,118,13,92,199,47,129,104,82,81,178,136,255,109,178,57,158,179,139,189,139,184,231,90,25,187,2,94,6,55,192, +77,240,108,27,99,95,7,223,3,63,7,111,182,145,162,40,33,133,41,251,49,218,237,54,222,251,0,187,146,98,244,70,138,212,166, +223,69,92,250,239,38,248,111,18,255,253,68,136,182,223,81,132,105,251,61,5,151,254,187,10,255,57,149,191,175,8,169,222, +88,226,55,86,159,247,108,55,2,61,218,231,217,249,243,4,83,189,103,89,254,204,170,244,121,247,229,239,55,66,50,158,63,23, +132,251,182,159,23,248,131,4,31,95,60,135,200,241,249,187,148,255,2,145,38,223,176,132,17,0,0}; + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getPlaybackInfo, "getPlaybackInfo", "()Landroid/media/session/MediaController$PlaybackInfo;") \ METHOD (getPlaybackState, "getPlaybackState", "()Landroid/media/session/PlaybackState;") \ METHOD (getTransportControls, "getTransportControls", "()Landroid/media/session/MediaController$TransportControls;") \ @@ -31,34 +104,28 @@ METHOD (setVolumeTo, "setVolumeTo", "(II)V") \ METHOD (unregisterCallback, "unregisterCallback", "(Landroid/media/session/MediaController$Callback;)V") -DECLARE_JNI_CLASS (AndroidMediaController, "android/media/session/MediaController") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") \ - -DECLARE_JNI_CLASS (AndroidMediaControllerCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MediaControllerCallback") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaController, "android/media/session/MediaController", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getAudioAttributes, "getAudioAttributes", "()Landroid/media/AudioAttributes;") \ METHOD (getCurrentVolume, "getCurrentVolume", "()I") \ METHOD (getMaxVolume, "getMaxVolume", "()I") -DECLARE_JNI_CLASS (AndroidMediaControllerPlaybackInfo, "android/media/session/MediaController$PlaybackInfo") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaControllerPlaybackInfo, "android/media/session/MediaController$PlaybackInfo", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (pause, "pause", "()V") \ METHOD (play, "play", "()V") \ METHOD (playFromMediaId, "playFromMediaId", "(Ljava/lang/String;Landroid/os/Bundle;)V") \ METHOD (seekTo, "seekTo", "(J)V") \ METHOD (stop, "stop", "()V") -DECLARE_JNI_CLASS (AndroidMediaControllerTransportControls, "android/media/session/MediaController$TransportControls") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaControllerTransportControls, "android/media/session/MediaController$TransportControls", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "()V") \ METHOD (getCurrentPosition, "getCurrentPosition", "()I") \ METHOD (getDuration, "getDuration", "()I") \ @@ -84,10 +151,10 @@ DECLARE_JNI_CLASS (AndroidMediaControllerTransportControls, "android/media/sessi METHOD (start, "start", "()V") \ METHOD (stop, "stop", "()V") -DECLARE_JNI_CLASS (AndroidMediaPlayer, "android/media/MediaPlayer") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaPlayer, "android/media/MediaPlayer", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(Landroid/content/Context;Ljava/lang/String;)V") \ METHOD (getController, "getController", "()Landroid/media/session/MediaController;") \ METHOD (release, "release", "()V") \ @@ -99,59 +166,44 @@ DECLARE_JNI_CLASS (AndroidMediaPlayer, "android/media/MediaPlayer") METHOD (setPlaybackState, "setPlaybackState", "(Landroid/media/session/PlaybackState;)V") \ METHOD (setPlaybackToLocal, "setPlaybackToLocal", "(Landroid/media/AudioAttributes;)V") -DECLARE_JNI_CLASS (AndroidMediaSession, "android/media/session/MediaSession") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") \ - -DECLARE_JNI_CLASS (AndroidMediaSessionCallback, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MediaSessionCallback") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaSession, "android/media/session/MediaSession", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (build, "build", "()Landroid/media/MediaMetadata;") \ METHOD (constructor, "", "()V") \ METHOD (putLong, "putLong", "(Ljava/lang/String;J)Landroid/media/MediaMetadata$Builder;") -DECLARE_JNI_CLASS (AndroidMediaMetadataBuilder, "android/media/MediaMetadata$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaMetadataBuilder, "android/media/MediaMetadata$Builder", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getSpeed, "getSpeed", "()F") \ METHOD (setSpeed, "setSpeed", "(F)Landroid/media/PlaybackParams;") -DECLARE_JNI_CLASS (AndroidPlaybackParams, "android/media/PlaybackParams") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidPlaybackParams, "android/media/PlaybackParams", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (getActions, "getActions", "()J") \ METHOD (getErrorMessage, "getErrorMessage", "()Ljava/lang/CharSequence;") \ METHOD (getPlaybackSpeed, "getPlaybackSpeed", "()F") \ METHOD (getPosition, "getPosition", "()J") \ METHOD (getState, "getState", "()I") -DECLARE_JNI_CLASS (AndroidPlaybackState, "android/media/session/PlaybackState") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidPlaybackState, "android/media/session/PlaybackState", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (build, "build", "()Landroid/media/session/PlaybackState;") \ METHOD (constructor, "", "()V") \ METHOD (setActions, "setActions", "(J)Landroid/media/session/PlaybackState$Builder;") \ METHOD (setErrorMessage, "setErrorMessage", "(Ljava/lang/CharSequence;)Landroid/media/session/PlaybackState$Builder;") \ METHOD (setState, "setState", "(IJF)Landroid/media/session/PlaybackState$Builder;") -DECLARE_JNI_CLASS (AndroidPlaybackStateBuilder, "android/media/session/PlaybackState$Builder") +DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidPlaybackStateBuilder, "android/media/session/PlaybackState$Builder", 21) #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";Landroid/app/Activity;J)V") \ - METHOD (setEnabled, "setEnabled", "(Z)V") - -DECLARE_JNI_CLASS (SystemVolumeObserver, JUCE_ANDROID_ACTIVITY_CLASSPATH "$SystemVolumeObserver") -#undef JNI_CLASS_MEMBERS - -#endif - //============================================================================== class MediaPlayerListener : public AndroidInterfaceImplementer { @@ -285,66 +337,70 @@ private: //============================================================================== struct VideoComponent::Pimpl - : public AndroidViewComponent -#if __ANDROID_API__ >= 21 - , private AppPausedResumedListener::Owner -#endif + : public AndroidViewComponent, private ActivityLifecycleCallbacks, private SurfaceHolderCallback { Pimpl (VideoComponent& ownerToUse, bool) - #if __ANDROID_API__ >= 21 : owner (ownerToUse), - mediaSession (*this), - appPausedResumedListener (*this), - appPausedResumedListenerNative (CreateJavaInterface (&appPausedResumedListener, - JUCE_ANDROID_ACTIVITY_CLASSPATH "$AppPausedResumedListener").get()) + mediaSession (*this) #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME , systemVolumeListener (*this) #endif - #endif { - #if __ANDROID_API__ >= 21 + // Video requires SDK version 21 or higher + jassert (getAndroidSDKVersion() >= 21); + setVisible (true); auto* env = getEnv(); - setView (LocalRef (env->CallObjectMethod (android.activity.get(), - JuceAppActivity.createNativeSurfaceView, - reinterpret_cast (this), - true))); + LocalRef appContext (getAppContext()); - env->CallVoidMethod (android.activity, JuceAppActivity.addAppPausedResumedListener, - appPausedResumedListenerNative.get(), reinterpret_cast (this)); - #endif + if (appContext != nullptr) + { + ActivityLifecycleCallbacks* callbacks = dynamic_cast (this); + + activityLifeListener = GlobalRef (CreateJavaInterface (callbacks, "android/app/Application$ActivityLifecycleCallbacks")); + env->CallVoidMethod (appContext.get(), AndroidApplication.registerActivityLifecycleCallbacks, activityLifeListener.get()); + } + + { + LocalRef surfaceView (env->NewObject (AndroidSurfaceView, AndroidSurfaceView.constructor, getAppContext().get())); + LocalRef holder (env->CallObjectMethod (surfaceView.get(), AndroidSurfaceView.getHolder)); + + SurfaceHolderCallback* callbacks = dynamic_cast (this); + surfaceHolderCallback = GlobalRef (CreateJavaInterface (callbacks, "android/view/SurfaceHolder$Callback")); + env->CallVoidMethod (holder, AndroidSurfaceHolder.addCallback, surfaceHolderCallback.get()); + + setView (surfaceView.get()); + } } ~Pimpl() { - #if __ANDROID_API__ >= 21 - getEnv()->CallVoidMethod (android.activity, JuceAppActivity.removeAppPausedResumedListener, - appPausedResumedListenerNative.get(), reinterpret_cast(this)); - #endif - } + auto* env = getEnv(); - #if __ANDROID_API__ < 21 - // Dummy implementations for unsupported API levels. - void loadAsync (const URL&, std::function) {} - void close() {} - bool isOpen() const noexcept { return false; } - bool isPlaying() const noexcept { return false; } - void play() {} - void stop() {} - void setPosition (double) {} - void setSpeed (double) {} - void setVolume (float) {} - float getVolume() const { return 0.0f; } - double getPosition() const { return 0.0; } - double getSpeed() const { return 0.0; } - Rectangle getNativeSize() const { return {}; } - double getDuration() const { return 0.0; } + if (surfaceHolderCallback != nullptr) + { + jobject view = reinterpret_cast (getView()); + + if (view != nullptr) + { + LocalRef holder (env->CallObjectMethod (view, AndroidSurfaceView.getHolder)); + + env->CallVoidMethod (holder, AndroidSurfaceHolder.removeCallback, surfaceHolderCallback.get()); + SurfaceHolderCallback::clear(); + surfaceHolderCallback.clear(); + } + } + + if (activityLifeListener != nullptr) + { + env->CallVoidMethod (getAppContext().get(), AndroidApplication.unregisterActivityLifecycleCallbacks, activityLifeListener.get()); + ActivityLifecycleCallbacks::clear(); + activityLifeListener.clear(); + } + } - File currentFile; - URL currentURL; - #else void loadAsync (const URL& url, std::function callback) { close(); @@ -358,10 +414,7 @@ struct VideoComponent::Pimpl if (! url.isLocalFile()) { - auto granted = android.activity.callBooleanMethod (JuceAppActivity.isPermissionDeclaredInManifestString, - javaString ("android.permission.INTERNET").get()) != 0; - - if (! granted) + if (! isPermissionDeclaredInManifest ("android.permission.INTERNET")) { // In order to access videos from the Internet, the Internet permission has to be specified in // Android Manifest. @@ -422,25 +475,23 @@ private: public: MediaSession (Pimpl& ownerToUse) : owner (ownerToUse), - sdkVersion (getEnv()->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion)), + sdkVersion (getAndroidSDKVersion()), audioAttributes (getAudioAttributes()), nativeMediaSession (LocalRef (getEnv()->NewObject (AndroidMediaSession, AndroidMediaSession.constructor, - android.activity.get(), + getAppContext().get(), javaString ("JuceVideoMediaSession").get()))), - mediaSessionCallback (LocalRef (getEnv()->NewObject (AndroidMediaSessionCallback, - AndroidMediaSessionCallback.constructor, - android.activity.get(), - reinterpret_cast (this)))), + mediaSessionCallback (createCallbackObject()), playbackStateBuilder (LocalRef (getEnv()->NewObject (AndroidPlaybackStateBuilder, AndroidPlaybackStateBuilder.constructor))), - controller (*this, getEnv()->CallObjectMethod (nativeMediaSession, - AndroidMediaSession.getController)), + controller (*this, LocalRef (getEnv()->CallObjectMethod (nativeMediaSession, + AndroidMediaSession.getController))), player (*this), - audioManager (android.activity.callObjectMethod (JuceAppActivity.getSystemService, javaString ("audio").get())), + audioManager (LocalRef (getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, + javaString ("audio").get()))), audioFocusChangeListener (*this), nativeAudioFocusChangeListener (GlobalRef (CreateJavaInterface (&audioFocusChangeListener, - "android/media/AudioManager$OnAudioFocusChangeListener").get())), + "android/media/AudioManager$OnAudioFocusChangeListener"))), audioFocusRequest (createAudioFocusRequestIfNecessary (sdkVersion, audioAttributes, nativeAudioFocusChangeListener)) { @@ -472,7 +523,7 @@ private: controller.closeVideo(); } - void setDisplay (jobject surfaceHolder) { player.setDisplay (surfaceHolder); } + void setDisplay (const LocalRef& surfaceHolder) { player.setDisplay (surfaceHolder); } void play() { controller.play(); } void stop() { controller.stop(); } @@ -575,15 +626,12 @@ private: class Controller { public: - Controller (MediaSession& ownerToUse, jobject nativeControllerToUse) + Controller (MediaSession& ownerToUse, const LocalRef& nativeControllerToUse) : owner (ownerToUse), nativeController (GlobalRef (nativeControllerToUse)), - controllerTransportControls (LocalRef (getEnv()->CallObjectMethod (nativeController, + controllerTransportControls (LocalRef (getEnv()->CallObjectMethod (nativeControllerToUse, AndroidMediaController.getTransportControls))), - controllerCallback (LocalRef (getEnv()->NewObject (AndroidMediaControllerCallback, - AndroidMediaControllerCallback.constructor, - android.activity.get(), - reinterpret_cast (this)))) + controllerCallback (createControllerCallbacks()) { auto* env = getEnv(); @@ -686,21 +734,7 @@ private: bool wasPaused = true; //============================================================================== - // MediaSessionController callbacks - - void audioInfoChanged (jobject info) - { - JUCE_VIDEO_LOG ("MediaSessionController::audioInfoChanged()"); - ignoreUnused (info); - } - - void metadataChanged (jobject metadata) - { - JUCE_VIDEO_LOG ("MediaSessionController::metadataChanged()"); - ignoreUnused (metadata); - } - - void playbackStateChanged (jobject playbackState) + void stateChanged (jobject playbackState) { JUCE_VIDEO_LOG ("MediaSessionController::playbackStateChanged()"); @@ -721,15 +755,55 @@ private: wasPaused = state == statePaused; } - void sessionDestroyed() + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + CALLBACK (audioInfoChanged, "mediaControllerAudioInfoChanged", "(JLandroid/media/session/MediaController$PlaybackInfo;)V") \ + CALLBACK (metadataChanged, "mediaControllerMetadataChanged", "(JLandroid/media/MediaMetadata;)V") \ + CALLBACK (playbackStateChanged, "mediaControllerPlaybackStateChanged", "(JLandroid/media/session/PlaybackState;)V") \ + CALLBACK (sessionDestroyed, "mediaControllerSessionDestroyed", "(J)V") + + DECLARE_JNI_CLASS_WITH_BYTECODE (AndroidMediaControllerCallback, "com/roli/juce/MediaControllerCallback", 21, MediaSessionByteCode, sizeof(MediaSessionByteCode)) + #undef JNI_CLASS_MEMBERS + + LocalRef createControllerCallbacks() { - JUCE_VIDEO_LOG ("MediaSessionController::sessionDestroyed()"); + return LocalRef (getEnv()->NewObject (AndroidMediaControllerCallback, + AndroidMediaControllerCallback.constructor, + reinterpret_cast (this))); } - friend void juce_mediaControllerAudioInfoChanged (int64, void*); - friend void juce_mediaControllerMetadataChanged (int64, void*); - friend void juce_mediaControllerPlaybackStateChanged (int64, void*); - friend void juce_mediaControllerSessionDestroyed (int64); + //============================================================================== + // MediaSessionController callbacks + static void audioInfoChanged (JNIEnv*, jobject, jlong host, jobject playbackInfo) + { + if (auto* myself = reinterpret_cast (host)) + { + ignoreUnused (playbackInfo); + JUCE_VIDEO_LOG ("MediaSessionController::audioInfoChanged()"); + } + } + + static void metadataChanged (JNIEnv*, jobject, jlong host, jobject metadata) + { + if (auto* myself = reinterpret_cast (host)) + { + ignoreUnused (metadata); + JUCE_VIDEO_LOG ("MediaSessionController::metadataChanged()"); + } + } + + static void playbackStateChanged (JNIEnv*, jobject, jlong host, jobject state) + { + if (auto* myself = reinterpret_cast (host)) + myself->stateChanged (state); + } + + static void sessionDestroyed (JNIEnv*, jobject, jlong host) + { + if (auto* myself = reinterpret_cast (host)) + JUCE_VIDEO_LOG ("MediaSessionController::sessionDestroyed()"); + } }; //============================================================================== @@ -744,7 +818,7 @@ private: {} - void setDisplay (jobject surfaceHolder) + void setDisplay (const LocalRef& surfaceHolder) { if (surfaceHolder == nullptr) { @@ -762,7 +836,7 @@ private: getEnv()->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDisplay, videoSurfaceHolder.get()); } - void load (jstring mediaId, jobject extras) + void load (const LocalRef& mediaId, const LocalRef& extras) { ignoreUnused (extras); @@ -774,8 +848,8 @@ private: currentState = State::idle; - auto uri = LocalRef (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, mediaId)); - env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDataSource, android.activity.get(), uri.get()); + auto uri = LocalRef (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, mediaId.get())); + env->CallVoidMethod (nativeMediaPlayer, AndroidMediaPlayer.setDataSource, getAppContext().get(), uri.get()); if (jniCheckHasExceptionOccurredAndClear()) { @@ -1193,7 +1267,6 @@ private: //============================================================================== Pimpl& owner; - int sdkVersion; GlobalRef audioAttributes; @@ -1221,68 +1294,98 @@ private: bool hasAudioFocus = false; //============================================================================== - // MediaSession callbacks - - void pauseCallback() + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(J)V") \ + CALLBACK (pauseCallback, "mediaSessionPause", "(J)V") \ + CALLBACK (playCallback, "mediaSessionPlay", "(J)V") \ + CALLBACK (playFromMediaIdCallback, "mediaSessionPlayFromMediaId", "(JLjava/lang/String;Landroid/os/Bundle;)V") \ + CALLBACK (seekToCallback, "mediaSessionSeekTo", "(JJ)V") \ + CALLBACK (stopCallback, "mediaSessionStop", "(J)V") + + DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidMediaSessionCallback, "com/roli/juce/MediaSessionCallback", 21) + #undef JNI_CLASS_MEMBERS + + LocalRef createCallbackObject() { - JUCE_VIDEO_LOG ("MediaSession::pauseCallback()"); + return LocalRef (getEnv()->NewObject (AndroidMediaSessionCallback, + AndroidMediaSessionCallback.constructor, + reinterpret_cast (this))); + } - player.pause(); - updatePlaybackState(); + //============================================================================== + // MediaSession callbacks + static void pauseCallback (JNIEnv*, jobject, jlong host) + { + if (auto* myself = reinterpret_cast (host)) + { + JUCE_VIDEO_LOG ("MediaSession::pauseCallback()"); + myself->player.pause(); + myself->updatePlaybackState(); - abandonAudioFocus(); + myself->abandonAudioFocus(); + } } - void playCallback() + static void playCallback (JNIEnv*, jobject, jlong host) { - JUCE_VIDEO_LOG ("MediaSession::playCallback()"); + if (auto* myself = reinterpret_cast (host)) + { + JUCE_VIDEO_LOG ("MediaSession::playCallback()"); - requestAudioFocus(); + myself->requestAudioFocus(); - if (! hasAudioFocus) - { - errorOccurred ("Application has been denied audio focus. Try again later."); - return; - } + if (! myself->hasAudioFocus) + { + myself->errorOccurred ("Application has been denied audio focus. Try again later."); + return; + } - getEnv()->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setActive, true); + getEnv()->CallVoidMethod (myself->nativeMediaSession, AndroidMediaSession.setActive, true); - player.play(); - setSpeed (playSpeedMult); - updatePlaybackState(); + myself->player.play(); + myself->setSpeed (myself->playSpeedMult); + myself->updatePlaybackState(); + } } - void playFromMediaIdCallback (jstring mediaId, jobject extras) + static void playFromMediaIdCallback (JNIEnv* env, jobject, jlong host, jstring mediaId, jobject extras) { - JUCE_VIDEO_LOG ("MediaSession::playFromMediaIdCallback()"); + if (auto* myself = reinterpret_cast (host)) + { + JUCE_VIDEO_LOG ("MediaSession::playFromMediaIdCallback()"); - player.load (mediaId, extras); - updatePlaybackState(); + myself->player.load (LocalRef ((jstring) env->NewLocalRef(mediaId)), LocalRef (env->NewLocalRef(extras))); + myself->updatePlaybackState(); + } } - void seekToCallback (jlong pos) + static void seekToCallback (JNIEnv* /*env*/, jobject, jlong host, jlong pos) { - JUCE_VIDEO_LOG ("MediaSession::seekToCallback()"); + if (auto* myself = reinterpret_cast (host)) + { + JUCE_VIDEO_LOG ("MediaSession::seekToCallback()"); - pendingSeekRequest = true; - player.setPlayPosition ((jint) pos); - updatePlaybackState(); + myself->pendingSeekRequest = true; + myself->player.setPlayPosition ((jint) pos); + myself->updatePlaybackState(); + } } - void stopCallback() + static void stopCallback(JNIEnv* env, jobject, jlong host) { - JUCE_VIDEO_LOG ("MediaSession::stopCallback()"); - - auto* env = getEnv(); + if (auto* myself = reinterpret_cast (host)) + { + JUCE_VIDEO_LOG ("MediaSession::stopCallback()"); - env->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setActive, false); + env->CallVoidMethod (myself->nativeMediaSession, AndroidMediaSession.setActive, false); - player.closeVideo(); - updatePlaybackState(); + myself->player.closeVideo(); + myself->updatePlaybackState(); - abandonAudioFocus(); + myself->abandonAudioFocus(); - owner.closeVideoFinished(); + myself->owner.closeVideoFinished(); + } } //============================================================================== @@ -1302,7 +1405,7 @@ private: auto playPos = player.getPlayPosition(); auto durationMs = player.getVideoDuration(); - auto playPosPercent = (int) (100 * playPos / static_cast (durationMs)); + int playPosPercent = 100 * playPos / static_cast (durationMs); // NB: assuming the playback will start roughly when there is 5% of content loaded... return ! bufferedRegions.containsRange (Range (playPosPercent, jmin (101, playPosPercent + 5))); @@ -1310,10 +1413,10 @@ private: void updatePlaybackState() { - getEnv()->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackState, getCurrentPlaybackState()); + getEnv()->CallVoidMethod (nativeMediaSession, AndroidMediaSession.setPlaybackState, getCurrentPlaybackState().get()); } - jobject getCurrentPlaybackState() + LocalRef getCurrentPlaybackState() { static constexpr int bufferingState = 6; @@ -1329,7 +1432,7 @@ private: LocalRef (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.setActions, (jint) allowedActions)); - return env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.build); + return LocalRef (env->CallObjectMethod (playbackStateBuilder, AndroidPlaybackStateBuilder.build)); } //============================================================================== @@ -1353,7 +1456,7 @@ private: auto playPos = player.getPlayPosition(); auto durationMs = player.getVideoDuration(); - auto playPosPercent = (int) (100 * playPos / static_cast (durationMs)); + int playPosPercent = 100 * playPos / static_cast (durationMs); bufferedRegions.addRange (Range (playPosPercent, progress + 1)); @@ -1373,8 +1476,12 @@ private: void playerPlaybackCompleted() { - pauseCallback(); - seekToCallback ((jlong) 0); + player.pause(); + abandonAudioFocus(); + + pendingSeekRequest = true; + player.setPlayPosition (0); + updatePlaybackState(); } void updateMetadata() @@ -1418,11 +1525,11 @@ private: } //============================================================================== - static jobject createAudioFocusRequestIfNecessary (int sdkVersion, const GlobalRef& audioAttributes, - const GlobalRef& nativeAudioFocusChangeListener) + static LocalRef createAudioFocusRequestIfNecessary (int sdkVersion, const GlobalRef& audioAttributes, + const GlobalRef& nativeAudioFocusChangeListener) { if (sdkVersion < 26) - return nullptr; + return LocalRef(); auto* env = getEnv(); @@ -1441,7 +1548,7 @@ private: LocalRef (env->CallObjectMethod (requestBuilder, setAudioAttributesMethod, audioAttributes.get())); LocalRef (env->CallObjectMethod (requestBuilder, setOnAudioFocusChangeListenerMethod, nativeAudioFocusChangeListener.get())); - return env->CallObjectMethod (requestBuilder, buildMethod); + return LocalRef (env->CallObjectMethod (requestBuilder, buildMethod)); } void requestAudioFocus() @@ -1540,8 +1647,11 @@ private: } //============================================================================== - static jobject getAudioAttributes() + static LocalRef getAudioAttributes() { + // Video requires SDK version 21 or higher + jassert (getAndroidSDKVersion() >= 21); + auto* env = getEnv(); auto audioAttribsBuilder = LocalRef (env->NewObject (AndroidAudioAttributesBuilder, @@ -1552,19 +1662,8 @@ private: LocalRef (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.setContentType, contentTypeMovie)); LocalRef (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.setUsage, usageMedia)); - return env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.build); + return LocalRef (env->CallObjectMethod (audioAttribsBuilder, AndroidAudioAttributesBuilder.build)); } - - friend void juce_mediaSessionPause (int64); - friend void juce_mediaSessionPlay (int64); - friend void juce_mediaSessionPlayFromMediaId (int64, void*, void*); - friend void juce_mediaSessionSeekTo (int64, int64); - friend void juce_mediaSessionStop (int64); - - friend void juce_mediaControllerAudioInfoChanged (int64, void*); - friend void juce_mediaControllerMetadataChanged (int64, void*); - friend void juce_mediaControllerPlaybackStateChanged (int64, void*); - friend void juce_mediaControllerSessionDestroyed (int64); }; #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME @@ -1574,11 +1673,7 @@ private: public: SystemVolumeListener (Pimpl& ownerToUse) : owner (ownerToUse), - nativeObserver (LocalRef (getEnv()->NewObject (SystemVolumeObserver, - SystemVolumeObserver.constructor, - android.activity.get(), - android.activity.get(), - reinterpret_cast (this)))) + nativeObserver (createCallbackObject()) { setEnabled (true); } @@ -1587,7 +1682,28 @@ private: { setEnabled (false); } + private: + Pimpl& owner; + GlobalRef nativeObserver; + + //============================================================================== + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (constructor, "", "(Landroid/app/Activity;J)V") \ + METHOD (setEnabled, "setEnabled", "(Z)V") \ + CALLBACK (systemVolumeChangedCallback, "mediaSessionSystemVolumeChanged", "(J)V") + DECLARE_JNI_CLASS_WITH_MIN_SDK (SystemVolumeObserver, "com/roli/juce/SystemVolumeObserver", 21) + #undef JNI_CLASS_MEMBERS + + + LocalRef createCallbackObject() + { + return LocalRef (getEnv()->NewObject (SystemVolumeObserver, + SystemVolumeObserver.constructor, + getCurrentActivity().get(), + reinterpret_cast (this))); + } + public: void setEnabled (bool shouldBeEnabled) { getEnv()->CallVoidMethod (nativeObserver, SystemVolumeObserver.setEnabled, shouldBeEnabled); @@ -1598,9 +1714,7 @@ private: } private: - Pimpl& owner; - GlobalRef nativeObserver; - + //============================================================================== void systemVolumeChanged() { WeakReference weakThis (this); @@ -1613,23 +1727,28 @@ private: if (weakThis->owner.owner.onGlobalMediaVolumeChanged != nullptr) weakThis->owner.owner.onGlobalMediaVolumeChanged(); }); + } - friend void juce_mediaSessionSystemVolumeChanged (int64); + //============================================================================== + static void systemVolumeChangedCallback (JNIEnv*, jobject, jlong host) + { + if (auto* myself = reinterpret_cast (host)) + myself->systemVolumeChanged(); + } JUCE_DECLARE_WEAK_REFERENCEABLE (SystemVolumeListener) }; - #endif //============================================================================== VideoComponent& owner; MediaSession mediaSession; - AppPausedResumedListener appPausedResumedListener; - GlobalRef appPausedResumedListenerNative; + GlobalRef activityLifeListener; #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME SystemVolumeListener systemVolumeListener; #endif + GlobalRef surfaceHolderCallback; std::function loadFinishedCallback; @@ -1670,18 +1789,23 @@ private: owner.onPlaybackStopped(); } - void videoSurfaceChanged (jobject surfaceHolder) + //============================================================================== + void surfaceChanged (LocalRef holder, int /*format*/, int /*width*/, int /*height*/) override { - mediaSession.setDisplay (surfaceHolder); + mediaSession.setDisplay (holder); } - void videoSurfaceDestroyed (jobject /*surfaceHolder*/) + void surfaceDestroyed (LocalRef /*holder*/) override + { + mediaSession.setDisplay (LocalRef()); + } + + void surfaceCreated (LocalRef /*holder*/) override { - mediaSession.setDisplay (nullptr); } //============================================================================== - void appPaused() override + void onActivityPaused (jobject) override { wasOpen = isOpen(); @@ -1698,7 +1822,7 @@ private: #endif } - void appResumed() override + void onActivityResumed (jobject) override { if (! wasOpen) return; @@ -1717,202 +1841,15 @@ private: } //============================================================================== - friend void juce_surfaceChangedNativeVideo (int64, void*); - friend void juce_surfaceDestroyedNativeVideo (int64, void*); - - friend void juce_mediaSessionPause (int64); - friend void juce_mediaSessionPlay (int64); - friend void juce_mediaSessionPlayFromMediaId (int64, void*, void*); - friend void juce_mediaSessionSeekTo (int64, int64); - friend void juce_mediaSessionStop (int64); - - friend void juce_mediaControllerAudioInfoChanged (int64, void*); - friend void juce_mediaControllerMetadataChanged (int64, void*); - friend void juce_mediaControllerPlaybackStateChanged (int64, void*); - friend void juce_mediaControllerSessionDestroyed (int64); - - friend void juce_mediaSessionSystemVolumeChanged (int64); #endif JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) }; -#if __ANDROID_API__ >= 21 //============================================================================== -void juce_surfaceChangedNativeVideo (int64 host, void* surfaceHolder) -{ - reinterpret_cast (host)->videoSurfaceChanged (static_cast (surfaceHolder)); -} - -void juce_surfaceDestroyedNativeVideo (int64 host, void* surfaceHolder) -{ - reinterpret_cast (host)->videoSurfaceDestroyed (static_cast (surfaceHolder)); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), dispatchDrawNativeVideo, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject canvas)) -{ - ignoreUnused (nativeView, host, canvas); - setEnv (env); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceChangedNativeVideo, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder, jint format, jint width, jint height)) -{ - ignoreUnused (nativeView, format, width, height); - setEnv (env); - - JUCE_VIDEO_LOG ("video surface changed"); - - juce_surfaceChangedNativeVideo (host, holder); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceCreatedNativeVideo, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder)) -{ - ignoreUnused (nativeView, host, holder); - setEnv (env); - - JUCE_VIDEO_LOG ("video surface created"); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeSurfaceView), surfaceDestroyedNativeVideo, - void, (JNIEnv* env, jobject nativeView, jlong host, jobject holder)) -{ - ignoreUnused (nativeView, host, holder); - setEnv (env); - - JUCE_VIDEO_LOG ("video surface destroyed"); - juce_surfaceDestroyedNativeVideo (host, holder); -} - -//============================================================================== -void juce_mediaSessionPause (int64 host) -{ - reinterpret_cast (host)->pauseCallback(); -} - -void juce_mediaSessionPlay (int64 host) -{ - reinterpret_cast (host)->playCallback(); -} - -void juce_mediaSessionPlayFromMediaId (int64 host, void* mediaId, void* extras) -{ - reinterpret_cast (host)->playFromMediaIdCallback ((jstring) mediaId, (jobject) extras); -} - -void juce_mediaSessionSeekTo (int64 host, int64 pos) -{ - reinterpret_cast (host)->seekToCallback (pos); -} - -void juce_mediaSessionStop (int64 host) -{ - reinterpret_cast (host)->stopCallback(); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionPause, - void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host)) -{ - setEnv (env); - juce_mediaSessionPause (host); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionPlay, - void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host)) -{ - setEnv (env); - juce_mediaSessionPlay (host); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionPlayFromMediaId, - void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host, jobject mediaId, jobject extras)) -{ - setEnv (env); - juce_mediaSessionPlayFromMediaId (host, mediaId, extras); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionSeekTo, - void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host, jlong pos)) -{ - setEnv (env); - juce_mediaSessionSeekTo (host, pos); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaSessionCallback), mediaSessionStop, - void, (JNIEnv* env, jobject /*mediaSessionCallback*/, jlong host)) -{ - setEnv (env); - juce_mediaSessionStop (host); -} - -//============================================================================== -void juce_mediaControllerAudioInfoChanged (int64 host, void* info) -{ - reinterpret_cast (host)->audioInfoChanged ((jobject) info); -} - -void juce_mediaControllerMetadataChanged (int64 host, void* metadata) -{ - reinterpret_cast (host)->metadataChanged ((jobject) metadata); -} - -void juce_mediaControllerPlaybackStateChanged (int64 host, void* state) -{ - reinterpret_cast (host)->playbackStateChanged ((jobject) state); -} - -void juce_mediaControllerSessionDestroyed (int64 host) -{ - reinterpret_cast (host)->sessionDestroyed(); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerAudioInfoChanged, - void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host, jobject playbackInfo)) -{ - setEnv (env); - juce_mediaControllerAudioInfoChanged (host, playbackInfo); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerMetadataChanged, - void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host, jobject metadata)) -{ - setEnv (env); - juce_mediaControllerMetadataChanged (host, metadata); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerPlaybackStateChanged, - void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host, jobject playbackState)) -{ - setEnv (env); - juce_mediaControllerPlaybackStateChanged (host, playbackState); -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024MediaControllerCallback), mediaControllerSessionDestroyed, - void, (JNIEnv* env, jobject /*mediaControllerCallback*/, jlong host)) -{ - setEnv (env); - juce_mediaControllerSessionDestroyed (host); -} - -//============================================================================== -void juce_mediaSessionSystemVolumeChanged (int64 host) -{ - #if JUCE_SYNC_VIDEO_VOLUME_WITH_OS_MEDIA_VOLUME - reinterpret_cast (host)->systemVolumeChanged(); - #else - ignoreUnused (host); - #endif -} - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024SystemVolumeObserver), mediaSessionSystemVolumeChanged, - void, (JNIEnv* env, jobject /*systemSettingsObserver*/, jlong host)) -{ - setEnv (env); - juce_mediaSessionSystemVolumeChanged (host); -} +constexpr VideoComponent::Pimpl::MediaSession::Player::StateInfo VideoComponent::Pimpl::MediaSession::Player::stateInfos[]; //============================================================================== -constexpr VideoComponent::Pimpl::MediaSession::Player::StateInfo VideoComponent::Pimpl::MediaSession::Player::stateInfos[]; -#endif +VideoComponent::Pimpl::MediaSession::AndroidMediaSessionCallback_Class VideoComponent::Pimpl::MediaSession::AndroidMediaSessionCallback; +VideoComponent::Pimpl::MediaSession::Controller::AndroidMediaControllerCallback_Class VideoComponent::Pimpl::MediaSession::Controller::AndroidMediaControllerCallback; +VideoComponent::Pimpl::SystemVolumeListener::SystemVolumeObserver_Class VideoComponent::Pimpl::SystemVolumeListener::SystemVolumeObserver; diff --git a/modules/juce_video/playback/juce_VideoComponent.h b/modules/juce_video/playback/juce_VideoComponent.h index 851fded020..d3fb329289 100644 --- a/modules/juce_video/playback/juce_VideoComponent.h +++ b/modules/juce_video/playback/juce_VideoComponent.h @@ -177,24 +177,6 @@ private: void resized() override; void timerCallback() override; - #if JUCE_ANDROID - friend void juce_surfaceChangedNativeVideo (int64, void*); - friend void juce_surfaceDestroyedNativeVideo (int64, void*); - - friend void juce_mediaSessionPause (int64); - friend void juce_mediaSessionPlay (int64); - friend void juce_mediaSessionPlayFromMediaId (int64, void*, void*); - friend void juce_mediaSessionSeekTo (int64, int64); - friend void juce_mediaSessionStop (int64); - - friend void juce_mediaControllerAudioInfoChanged (int64, void*); - friend void juce_mediaControllerMetadataChanged (int64, void*); - friend void juce_mediaControllerPlaybackStateChanged (int64, void*); - friend void juce_mediaControllerSessionDestroyed (int64); - - friend void juce_mediaSessionSystemVolumeChanged (int64); - #endif - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VideoComponent) };