From 08af009062d1e542162e7f071f7f667a721235a5 Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 13 Jul 2022 01:04:44 +0100 Subject: [PATCH] Update to JUCE 7.0.1, still to update to new playhead APIs Signed-off-by: falkTX --- data/copy-juce | 28 - data/update-juce | 33 + source/backend/plugin/CarlaPluginJuce.cpp | 9 +- source/modules/AppConfig.h | 2 + .../audio_play_head/juce_AudioPlayHead.h | 323 ++- .../buffers/juce_AudioSampleBuffer.h | 93 +- .../juce_audio_basics/juce_audio_basics.cpp | 62 +- .../juce_audio_basics/juce_audio_basics.h | 4 +- .../sources/juce_BufferingAudioSource.cpp | 4 +- .../juce_audio_devices/juce_audio_devices.cpp | 4 +- .../juce_audio_devices/juce_audio_devices.h | 2 +- .../native/juce_android_Audio.cpp | 470 ---- ...juce_android_HighPerformanceAudioHelpers.h | 131 - .../native/juce_android_Midi.cpp | 701 ------ .../native/juce_android_Oboe.cpp | 1439 ----------- .../native/juce_android_OpenSL.cpp | 1292 ---------- .../native/juce_ios_Audio.cpp | 1495 ------------ .../native/juce_ios_Audio.h | 92 - .../LV2_SDK/generate_lv2_bundle_sources.py | 10 +- .../format_types/juce_ARAHosting.h | 31 + .../format_types/juce_AU_Shared.h | 8 +- .../juce_AudioUnitPluginFormat.mm | 24 +- .../format_types/juce_LV2Common.h | 15 +- .../format_types/juce_LV2PluginFormat.cpp | 334 +-- .../format_types/juce_LV2Resources.h | 4 + .../format_types/juce_VST3Common.h | 495 ++-- .../format_types/juce_VST3PluginFormat.cpp | 280 +-- .../juce_VST3PluginFormat_test.cpp | 433 ++-- .../format_types/juce_VSTPluginFormat.cpp | 91 +- .../juce_audio_processors.cpp | 69 +- .../juce_audio_processors.h | 2 +- .../processors/juce_AudioProcessor.h | 53 - .../processors/juce_AudioProcessorEditor.cpp | 3 - .../processors/juce_AudioProcessorEditor.h | 3 - .../processors/juce_AudioProcessorGraph.cpp | 24 +- .../ARA/juce_ARADocumentController.h | 24 +- .../utilities/ARA/juce_ARAModelObjects.h | 10 +- .../ARA/juce_ARAPlugInInstanceRoles.cpp | 2 +- .../ARA/juce_ARAPlugInInstanceRoles.h | 8 +- .../ARA/juce_AudioProcessor_ARAExtensions.cpp | 12 +- .../ARA/juce_AudioProcessor_ARAExtensions.h | 2 +- .../utilities/juce_ExtensionsVisitor.h | 8 + .../juce_core/containers/juce_Optional.h | 59 +- .../juce_core/files/juce_AndroidDocument.h | 476 ++++ source/modules/juce_core/files/juce_File.cpp | 7 + source/modules/juce_core/files/juce_File.h | 6 + .../files}/juce_common_MimeTypes.cpp | 23 +- .../files/juce_common_MimeTypes.h} | 26 +- source/modules/juce_core/juce_core.cpp | 44 +- source/modules/juce_core/juce_core.h | 4 +- .../native/juce_BasicNativeHeaders.h | 2 + .../juce_core/native/juce_android_Files.cpp | 691 ------ .../native/juce_android_JNIHelpers.cpp | 701 ------ .../native/juce_android_JNIHelpers.h | 1010 -------- .../juce_core/native/juce_android_Misc.cpp | 44 - .../juce_core/native/juce_android_Network.cpp | 656 ----- .../juce_android_RuntimePermissions.cpp | 261 -- .../native/juce_android_SystemStats.cpp | 241 -- .../juce_core/native/juce_android_Threads.cpp | 395 --- .../juce_core/native/juce_mac_ObjCHelpers.h | 2 +- .../juce_core/native/juce_posix_SharedCode.h | 6 + .../juce_core/native/juce_win32_ComSmartPtr.h | 2 +- .../juce_core/native/juce_win32_Files.cpp | 103 +- source/modules/juce_core/network/juce_URL.cpp | 9 +- .../juce_core/system/juce_CompilerSupport.h | 4 +- .../juce_core/system/juce_StandardHeader.h | 11 +- .../juce_core/threads/juce_ChildProcess.cpp | 2 +- .../juce_core/threads/juce_ChildProcess.h | 2 +- .../juce_data_structures.h | 2 +- .../juce_ConnectedChildProcess.cpp | 4 + source/modules/juce_events/juce_events.cpp | 12 +- source/modules/juce_events/juce_events.h | 2 +- .../messages/juce_MessageManager.cpp | 2 + .../native/juce_android_Messaging.cpp | 302 --- .../native/juce_ios_MessageManager.mm | 103 - .../modules/juce_graphics/juce_graphics.cpp | 8 +- source/modules/juce_graphics/juce_graphics.h | 2 +- .../native/juce_android_Fonts.cpp | 549 ----- .../native/juce_android_GraphicsContext.cpp | 66 - .../juce_AccessibilityTextInterface.h | 3 + .../juce_AccessibilityHandler.cpp | 1 - .../components/juce_Component.cpp | 22 +- .../juce_gui_basics/juce_gui_basics.cpp | 129 +- .../modules/juce_gui_basics/juce_gui_basics.h | 2 +- .../juce_gui_basics/layout/juce_Grid.cpp | 15 +- .../juce_gui_basics/layout/juce_Viewport.cpp | 27 +- .../juce_gui_basics/layout/juce_Viewport.h | 12 +- .../juce_gui_basics/menus/juce_PopupMenu.cpp | 5 +- .../mouse/juce_DragAndDropContainer.cpp | 12 +- .../juce_AccessibilityTextHelpers.h | 272 ++- .../juce_android_Accessibility.cpp | 275 ++- .../juce_win32_Accessibility.cpp | 10 +- .../accessibility/juce_win32_UIAHelpers.h | 14 + .../juce_win32_UIATextProvider.h | 104 +- .../native/juce_android_ContentSharer.cpp | 900 ------- .../native/juce_android_FileChooser.cpp | 240 -- .../native/juce_android_Windowing.cpp | 2108 ----------------- .../native/juce_ios_ContentSharer.cpp | 211 -- .../native/juce_ios_FileChooser.mm | 412 ---- .../native/juce_ios_UIViewComponentPeer.mm | 1400 ----------- .../native/juce_ios_Windowing.mm | 844 ------- .../native/juce_linux_FileChooser.cpp | 2 + .../native/juce_mac_CGMetalLayerRenderer.h | 243 +- .../native/juce_mac_NSViewComponentPeer.mm | 111 +- .../native/juce_win32_Windowing.cpp | 170 +- .../native/x11/juce_linux_XWindowSystem.cpp | 47 +- .../juce_gui_basics/widgets/juce_Label.cpp | 3 - .../juce_gui_basics/widgets/juce_Slider.cpp | 53 +- .../juce_gui_basics/widgets/juce_Slider.h | 2 + .../widgets/juce_TextEditor.cpp | 73 +- .../juce_gui_basics/widgets/juce_TextEditor.h | 19 + .../widgets/juce_ToolbarItemComponent.cpp | 2 +- .../juce_gui_basics/widgets/juce_TreeView.cpp | 2 +- .../windows/juce_ComponentPeer.cpp | 23 +- .../windows/juce_ComponentPeer.h | 40 +- .../code_editor/juce_CodeEditorComponent.cpp | 11 +- .../modules/juce_gui_extra/juce_gui_extra.cpp | 32 +- .../modules/juce_gui_extra/juce_gui_extra.h | 2 +- .../misc/juce_ColourSelector.cpp | 4 - .../native/juce_android_PushNotifications.cpp | 1648 ------------- .../juce_android_WebBrowserComponent.cpp | 725 ------ .../native/juce_ios_PushNotifications.cpp | 998 -------- .../native/juce_ios_UIViewComponent.mm | 132 -- 123 files changed, 3397 insertions(+), 21931 deletions(-) delete mode 100755 data/copy-juce create mode 100755 data/update-juce delete mode 100644 source/modules/juce_audio_devices/native/juce_android_Audio.cpp delete mode 100644 source/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h delete mode 100644 source/modules/juce_audio_devices/native/juce_android_Midi.cpp delete mode 100644 source/modules/juce_audio_devices/native/juce_android_Oboe.cpp delete mode 100644 source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp delete mode 100644 source/modules/juce_audio_devices/native/juce_ios_Audio.cpp delete mode 100644 source/modules/juce_audio_devices/native/juce_ios_Audio.h create mode 100644 source/modules/juce_core/files/juce_AndroidDocument.h rename source/modules/{juce_gui_basics/native => juce_core/files}/juce_common_MimeTypes.cpp (94%) rename source/modules/{juce_graphics/native/juce_android_IconHelpers.cpp => juce_core/files/juce_common_MimeTypes.h} (53%) delete mode 100644 source/modules/juce_core/native/juce_android_Files.cpp delete mode 100644 source/modules/juce_core/native/juce_android_JNIHelpers.cpp delete mode 100644 source/modules/juce_core/native/juce_android_JNIHelpers.h delete mode 100644 source/modules/juce_core/native/juce_android_Misc.cpp delete mode 100644 source/modules/juce_core/native/juce_android_Network.cpp delete mode 100644 source/modules/juce_core/native/juce_android_RuntimePermissions.cpp delete mode 100644 source/modules/juce_core/native/juce_android_SystemStats.cpp delete mode 100644 source/modules/juce_core/native/juce_android_Threads.cpp delete mode 100644 source/modules/juce_events/native/juce_android_Messaging.cpp delete mode 100644 source/modules/juce_events/native/juce_ios_MessageManager.mm delete mode 100644 source/modules/juce_graphics/native/juce_android_Fonts.cpp delete mode 100644 source/modules/juce_graphics/native/juce_android_GraphicsContext.cpp delete mode 100644 source/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp delete mode 100644 source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp delete mode 100644 source/modules/juce_gui_basics/native/juce_android_Windowing.cpp delete mode 100644 source/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp delete mode 100644 source/modules/juce_gui_basics/native/juce_ios_FileChooser.mm delete mode 100644 source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm delete mode 100644 source/modules/juce_gui_basics/native/juce_ios_Windowing.mm delete mode 100644 source/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp delete mode 100644 source/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp delete mode 100644 source/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp delete mode 100644 source/modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm diff --git a/data/copy-juce b/data/copy-juce deleted file mode 100755 index a72ac3408..000000000 --- a/data/copy-juce +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -e - -JUCE_MODULES_DIR="/Shared/Personal/FOSS/GIT/DISTRHO/juce/modules" -CARLA_MODULES_DIR="/Shared/Personal/FOSS/GIT/falkTX/Carla/source/modules" - -MODULES=("juce_audio_basics juce_audio_devices juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") - -for M in $MODULES; do - echo $CARLA_MODULES_DIR/$M; - rm -f $CARLA_MODULES_DIR/$M/juce_* - rm -rf $CARLA_MODULES_DIR/$M/{a..z}* - cp -r -v $JUCE_MODULES_DIR/$M/* $CARLA_MODULES_DIR/$M/ - rm $CARLA_MODULES_DIR/$M/juce_*.mm -done - -find $CARLA_MODULES_DIR -name juce_module_info -delete -rm -rf $CARLA_MODULES_DIR/juce_*/native/java/ -rm -rf $CARLA_MODULES_DIR/juce_*/native/javacore/ -rm -rf $CARLA_MODULES_DIR/juce_*/native/javaopt/ -rm -rf $CARLA_MODULES_DIR/juce_*/native/oboe/ - -sed -i "s|Juce VST Host|Carla Plugin Host|" $CARLA_MODULES_DIR/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp - -rm -rf $CARLA_MODULES_DIR/../includes/vst3sdk -mv $CARLA_MODULES_DIR/juce_audio_processors/format_types/VST3_SDK $CARLA_MODULES_DIR/../includes/vst3sdk -rm -rf $CARLA_MODULES_DIR/../includes/vst3sdk/*.pdf diff --git a/data/update-juce b/data/update-juce new file mode 100755 index 000000000..756503d2b --- /dev/null +++ b/data/update-juce @@ -0,0 +1,33 @@ +#!/bin/bash + +set -e + +cd $(dirname ${0}) +cd .. + +CARLA_DIR=$(pwd) +DISTRHO_PORTS_DIR=/tmp/distrho-ports-carla + +rm -rf ${DISTRHO_PORTS_DIR} +git clone git@github.com:DISTRHO/DISTRHO-Ports.git ${DISTRHO_PORTS_DIR} --depth=1 + +CARLA_MODULES_DIR=${CARLA_DIR}/source/modules +JUCE_MODULES_DIR=${DISTRHO_PORTS_DIR}/libs/juce7/source/modules + +MODULES=("juce_audio_basics juce_audio_devices juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") + +for M in ${MODULES}; do + echo ${CARLA_MODULES_DIR}/${M}; + rm -f ${CARLA_MODULES_DIR}/${M}/juce_* + rm -rf ${CARLA_MODULES_DIR}/${M}/{a..z}* + cp -r -v ${JUCE_MODULES_DIR}/${M}/* ${CARLA_MODULES_DIR}/${M}/ + rm ${CARLA_MODULES_DIR}/${M}/juce_*.mm +done + +find ${CARLA_MODULES_DIR} -name juce_module_info -delete + +rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk +mv ${CARLA_MODULES_DIR}/juce_audio_processors/format_types/VST3_SDK ${CARLA_MODULES_DIR}/../includes/vst3sdk +rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk/*.pdf + +# rm -rf ${DISTRHO_PORTS_DIR} diff --git a/source/backend/plugin/CarlaPluginJuce.cpp b/source/backend/plugin/CarlaPluginJuce.cpp index 4992d8c05..e7f55152d 100644 --- a/source/backend/plugin/CarlaPluginJuce.cpp +++ b/source/backend/plugin/CarlaPluginJuce.cpp @@ -503,9 +503,12 @@ public: ? (AEffect*)fInstance->getPlatformSpecificData() : nullptr; + /* TODO update to juce7 APIs v3_plugin_view** const vst3view = fDesc.pluginFormatName == "VST3" ? (v3_plugin_view**)editor->getPlatformSpecificData() : nullptr; + */ + v3_plugin_view** const vst3view = nullptr; fWindow = new JucePluginWindow(opts.frontendWinId, opts.pluginsAreStandalone, vst2effect, vst3view); @@ -1553,10 +1556,12 @@ protected: pData->engine->touchPluginParameter(pData->id, static_cast(index), false); } - bool getCurrentPosition(CurrentPositionInfo& result) override + juce::Optional getPosition() const override { + /* TODO update to juce7 APIs carla_copyStruct(result, fPosInfo); - return true; + */ + return {}; } // ------------------------------------------------------------------- diff --git a/source/modules/AppConfig.h b/source/modules/AppConfig.h index 0f17e69ab..685a7c04e 100644 --- a/source/modules/AppConfig.h +++ b/source/modules/AppConfig.h @@ -203,6 +203,8 @@ // -------------------------------------------------------------------------------------------------------------------- // juce_audio_processors +#define JUCE_VST_FALLBACK_HOST_NAME "Carla" + //============================================================================= /** Config: JUCE_PLUGINHOST_VST Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be diff --git a/source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h b/source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h index f3a265e1a..51907c327 100644 --- a/source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h +++ b/source/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h @@ -152,8 +152,60 @@ public: bool drop = false, pulldown = false; }; + /** Describes a musical time signature. + + @see PositionInfo::getTimeSignature() PositionInfo::setTimeSignature() + */ + struct JUCE_API TimeSignature + { + /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ + int numerator = 4; + + /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ + int denominator = 4; + + bool operator== (const TimeSignature& other) const + { + const auto tie = [] (auto& x) { return std::tie (x.numerator, x.denominator); }; + return tie (*this) == tie (other); + } + + bool operator!= (const TimeSignature& other) const + { + return ! operator== (other); + } + }; + + /** Holds the begin and end points of a looped region. + + @see PositionInfo::getIsLooping() PositionInfo::setIsLooping() PositionInfo::getLoopPoints() PositionInfo::setLoopPoints() + */ + struct JUCE_API LoopPoints + { + /** The current cycle start position in units of quarter-notes. */ + double ppqStart = 0; + + /** The current cycle end position in units of quarter-notes. */ + double ppqEnd = 0; + + bool operator== (const LoopPoints& other) const + { + const auto tie = [] (auto& x) { return std::tie (x.ppqStart, x.ppqEnd); }; + return tie (*this) == tie (other); + } + + bool operator!= (const LoopPoints& other) const + { + return ! operator== (other); + } + }; + //============================================================================== - /** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. + /** This type is deprecated; prefer PositionInfo instead. + + Some position info may be unavailable, depending on the host or plugin format. + Unfortunately, CurrentPositionInfo doesn't have any way of differentiating between + default values and values that have been set explicitly. */ struct JUCE_API CurrentPositionInfo { @@ -162,6 +214,7 @@ public: /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ int timeSigNumerator = 4; + /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ int timeSigDenominator = 4; @@ -248,7 +301,208 @@ public: }; //============================================================================== - /** Fills-in the given structure with details about the transport's + /** + Describes the time at the start of the current audio callback. + + Not all hosts and plugin formats can provide all of the possible time + information, so most of the getter functions in this class return + an Optional that will only be engaged if the host provides the corresponding + information. As a plugin developer, you should code defensively so that + the plugin behaves sensibly even when the host fails to provide timing + information. + + A default-constructed instance of this class will return nullopt from + all functions that return an Optional. + */ + class PositionInfo + { + public: + /** Returns the number of samples that have elapsed. */ + Optional getTimeInSamples() const { return getOptional (flagTimeSamples, timeInSamples); } + + /** @see getTimeInSamples() */ + void setTimeInSamples (Optional timeInSamplesIn) { setOptional (flagTimeSamples, timeInSamples, timeInSamplesIn); } + + /** Returns the number of seconds that have elapsed. */ + Optional getTimeInSeconds() const { return getOptional (flagTimeSeconds, timeInSeconds); } + + /** @see getTimeInSamples() */ + void setTimeInSeconds (Optional timeInSecondsIn) { setOptional (flagTimeSeconds, timeInSeconds, timeInSecondsIn); } + + /** Returns the bpm, if available. */ + Optional getBpm() const { return getOptional (flagTempo, tempoBpm); } + + /** @see getBpm() */ + void setBpm (Optional bpmIn) { setOptional (flagTempo, tempoBpm, bpmIn); } + + /** Returns the time signature, if available. */ + Optional getTimeSignature() const { return getOptional (flagTimeSignature, timeSignature); } + + /** @see getTimeSignature() */ + void setTimeSignature (Optional timeSignatureIn) { setOptional (flagTimeSignature, timeSignature, timeSignatureIn); } + + /** Returns host loop points, if available. */ + Optional getLoopPoints() const { return getOptional (flagLoopPoints, loopPoints); } + + /** @see getLoopPoints() */ + void setLoopPoints (Optional loopPointsIn) { setOptional (flagLoopPoints, loopPoints, loopPointsIn); } + + /** The number of bars since the beginning of the timeline. + + This value isn't available in all hosts or in all plugin formats. + */ + Optional getBarCount() const { return getOptional (flagBarCount, barCount); } + + /** @see getBarCount() */ + void setBarCount (Optional barCountIn) { setOptional (flagBarCount, barCount, barCountIn); } + + /** The position of the start of the last bar, in units of quarter-notes. + + This is the time from the start of the timeline to the start of the current + bar, in ppq units. + + Note - this value may be unavailable on some hosts, e.g. Pro-Tools. + */ + Optional getPpqPositionOfLastBarStart() const { return getOptional (flagLastBarStartPpq, lastBarStartPpq); } + + /** @see getPpqPositionOfLastBarStart() */ + void setPpqPositionOfLastBarStart (Optional positionIn) { setOptional (flagLastBarStartPpq, lastBarStartPpq, positionIn); } + + /** The video frame rate, if available. */ + Optional getFrameRate() const { return getOptional (flagFrameRate, frame); } + + /** @see getFrameRate() */ + void setFrameRate (Optional frameRateIn) { setOptional (flagFrameRate, frame, frameRateIn); } + + /** The current play position, in units of quarter-notes. */ + Optional getPpqPosition() const { return getOptional (flagPpqPosition, positionPpq); } + + /** @see getPpqPosition() */ + void setPpqPosition (Optional ppqPositionIn) { setOptional (flagPpqPosition, positionPpq, ppqPositionIn); } + + /** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ + Optional getEditOriginTime() const { return getOptional (flagOriginTime, originTime); } + + /** @see getEditOriginTime() */ + void setEditOriginTime (Optional editOriginTimeIn) { setOptional (flagOriginTime, originTime, editOriginTimeIn); } + + /** Get the host's callback time in nanoseconds, if available. */ + Optional getHostTimeNs() const { return getOptional (flagHostTimeNs, hostTimeNs); } + + /** @see getHostTimeNs() */ + void setHostTimeNs (Optional hostTimeNsIn) { setOptional (flagHostTimeNs, hostTimeNs, hostTimeNsIn); } + + /** True if the transport is currently playing. */ + bool getIsPlaying() const { return getFlag (flagIsPlaying); } + + /** @see getIsPlaying() */ + void setIsPlaying (bool isPlayingIn) { setFlag (flagIsPlaying, isPlayingIn); } + + /** True if the transport is currently recording. + + (When isRecording is true, then isPlaying will also be true). + */ + bool getIsRecording() const { return getFlag (flagIsRecording); } + + /** @see getIsRecording() */ + void setIsRecording (bool isRecordingIn) { setFlag (flagIsRecording, isRecordingIn); } + + /** True if the transport is currently looping. */ + bool getIsLooping() const { return getFlag (flagIsLooping); } + + /** @see getIsLooping() */ + void setIsLooping (bool isLoopingIn) { setFlag (flagIsLooping, isLoopingIn); } + + bool operator== (const PositionInfo& other) const noexcept + { + const auto tie = [] (const PositionInfo& i) + { + return std::make_tuple (i.getTimeInSamples(), + i.getTimeInSeconds(), + i.getPpqPosition(), + i.getEditOriginTime(), + i.getPpqPositionOfLastBarStart(), + i.getFrameRate(), + i.getBarCount(), + i.getTimeSignature(), + i.getBpm(), + i.getLoopPoints(), + i.getHostTimeNs(), + i.getIsPlaying(), + i.getIsRecording(), + i.getIsLooping()); + }; + + return tie (*this) == tie (other); + } + + bool operator!= (const PositionInfo& other) const noexcept + { + return ! operator== (other); + } + + private: + bool getFlag (int64_t flagToCheck) const + { + return (flagToCheck & flags) != 0; + } + + void setFlag (int64_t flagToCheck, bool value) + { + flags = (value ? flags | flagToCheck : flags & ~flagToCheck); + } + + template + Optional getOptional (int64_t flagToCheck, Value value) const + { + return getFlag (flagToCheck) ? makeOptional (std::move (value)) : nullopt; + } + + template + void setOptional (int64_t flagToCheck, Value& value, Optional opt) + { + if (opt.hasValue()) + value = *opt; + + setFlag (flagToCheck, opt.hasValue()); + } + + enum + { + flagTimeSignature = 1 << 0, + flagLoopPoints = 1 << 1, + flagFrameRate = 1 << 2, + flagTimeSeconds = 1 << 3, + flagLastBarStartPpq = 1 << 4, + flagPpqPosition = 1 << 5, + flagOriginTime = 1 << 6, + flagTempo = 1 << 7, + flagTimeSamples = 1 << 8, + flagBarCount = 1 << 9, + flagHostTimeNs = 1 << 10, + flagIsPlaying = 1 << 11, + flagIsRecording = 1 << 12, + flagIsLooping = 1 << 13 + }; + + TimeSignature timeSignature; + LoopPoints loopPoints; + FrameRate frame = FrameRateType::fps23976; + double timeInSeconds = 0.0; + double lastBarStartPpq = 0.0; + double positionPpq = 0.0; + double originTime = 0.0; + double tempoBpm = 0.0; + int64_t timeInSamples = 0; + int64_t barCount = 0; + uint64_t hostTimeNs = 0; + int64_t flags = 0; + }; + + //============================================================================== + /** Deprecated, use getPosition() instead. + + Fills-in the given structure with details about the transport's position at the start of the current processing block. If this method returns false then the current play head position is not available and the given structure will be undefined. @@ -258,7 +512,70 @@ public: in which a time would make sense, and some hosts will almost certainly have multithreading issues if it's not called on the audio thread. */ - virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0; + [[deprecated ("Use getPosition instead. Not all hosts are able to provide all time position information; getPosition differentiates clearly between set and unset fields.")]] + bool getCurrentPosition (CurrentPositionInfo& result) + { + if (const auto pos = getPosition()) + { + result.resetToDefault(); + + if (const auto sig = pos->getTimeSignature()) + { + result.timeSigNumerator = sig->numerator; + result.timeSigDenominator = sig->denominator; + } + + if (const auto loop = pos->getLoopPoints()) + { + result.ppqLoopStart = loop->ppqStart; + result.ppqLoopEnd = loop->ppqEnd; + } + + if (const auto frame = pos->getFrameRate()) + result.frameRate = *frame; + + if (const auto timeInSeconds = pos->getTimeInSeconds()) + result.timeInSeconds = *timeInSeconds; + + if (const auto lastBarStartPpq = pos->getPpqPositionOfLastBarStart()) + result.ppqPositionOfLastBarStart = *lastBarStartPpq; + + if (const auto ppqPosition = pos->getPpqPosition()) + result.ppqPosition = *ppqPosition; + + if (const auto originTime = pos->getEditOriginTime()) + result.editOriginTime = *originTime; + + if (const auto bpm = pos->getBpm()) + result.bpm = *bpm; + + if (const auto timeInSamples = pos->getTimeInSamples()) + result.timeInSamples = *timeInSamples; + + result.isPlaying = pos->getIsPlaying(); + result.isRecording = pos->getIsRecording(); + result.isLooping = pos->getIsLooping(); + + return true; + } + + return false; + } + + /** Fetches details about the transport's position at the start of the current + processing block. If this method returns nullopt then the current play head + position is not available. + + A non-null return value just indicates that the host was able to provide + *some* relevant timing information. Individual PositionInfo getters may + still return nullopt. + + You can ONLY call this from your processBlock() method! Calling it at other + times will produce undefined behaviour, as the host may not have any context + in which a time would make sense, and some hosts will almost certainly have + multithreading issues if it's not called on the audio thread. + */ + virtual Optional getPosition() const = 0; /** Returns true if this object can control the transport. */ virtual bool canControlTransport() { return false; } diff --git a/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index 451dc608f..9ea81f51d 100644 --- a/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/source/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -23,53 +23,6 @@ namespace juce { -#ifndef DOXYGEN -/** The contents of this namespace are used to implement AudioBuffer and should - not be used elsewhere. Their interfaces (and existence) are liable to change! -*/ -namespace detail -{ - /** On iOS/arm7 the alignment of `double` is greater than the alignment of - `std::max_align_t`, so we can't trust max_align_t. Instead, we query - lots of primitive types and use the maximum alignment of all of them. - - We're putting this stuff outside AudioBuffer itself to avoid creating - unnecessary copies for each distinct template instantiation of - AudioBuffer. - - MSVC 2015 doesn't like when we write getMaxAlignment as a loop which - accumulates the max alignment (declarations not allowed in constexpr - function body) so instead we use this recursive version which - instantiates a zillion templates. - */ - - template struct Type {}; - - constexpr size_t getMaxAlignment() noexcept { return 0; } - - template - constexpr size_t getMaxAlignment (Type, Type... tail) noexcept - { - return jmax (alignof (Head), getMaxAlignment (tail...)); - } - - constexpr size_t maxAlignment = getMaxAlignment (Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}, - Type{}); -} // namespace detail -#endif - //============================================================================== /** A multi-channel buffer containing floating point audio samples. @@ -1215,17 +1168,10 @@ public: private: //============================================================================== - int numChannels = 0, size = 0; - size_t allocatedBytes = 0; - Type** channels; - HeapBlock allocatedData; - Type* preallocatedChannelSpace[32]; - bool isClear = false; - void allocateData() { #if (! JUCE_GCC || (__GNUC__ * 100 + __GNUC_MINOR__) >= 409) - static_assert (alignof (Type) <= detail::maxAlignment, + static_assert (alignof (Type) <= maxAlignment, "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); #endif jassert (size >= 0); @@ -1278,6 +1224,43 @@ private: isClear = false; } + /* On iOS/arm7 the alignment of `double` is greater than the alignment of + `std::max_align_t`, so we can't trust max_align_t. Instead, we query + lots of primitive types and use the maximum alignment of all of them. + */ + static constexpr size_t getMaxAlignment() noexcept + { + constexpr size_t alignments[] { alignof (std::max_align_t), + alignof (void*), + alignof (float), + alignof (double), + alignof (long double), + alignof (short int), + alignof (int), + alignof (long int), + alignof (long long int), + alignof (bool), + alignof (char), + alignof (char16_t), + alignof (char32_t), + alignof (wchar_t) }; + + size_t max = 0; + + for (const auto elem : alignments) + max = jmax (max, elem); + + return max; + } + + int numChannels = 0, size = 0; + size_t allocatedBytes = 0; + Type** channels; + HeapBlock allocatedData; + Type* preallocatedChannelSpace[32]; + bool isClear = false; + static constexpr size_t maxAlignment = getMaxAlignment(); + JUCE_LEAK_DETECTOR (AudioBuffer) }; diff --git a/source/modules/juce_audio_basics/juce_audio_basics.cpp b/source/modules/juce_audio_basics/juce_audio_basics.cpp index 8d4650e8d..10f3a9bbc 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/source/modules/juce_audio_basics/juce_audio_basics.cpp @@ -31,8 +31,6 @@ #include "juce_audio_basics.h" -#include - #if JUCE_MINGW && ! defined (alloca) #define alloca __builtin_alloca #endif @@ -55,46 +53,46 @@ #include #endif -// #include "buffers/juce_AudioDataConverters.cpp" +#include "buffers/juce_AudioDataConverters.cpp" #include "buffers/juce_FloatVectorOperations.cpp" #include "buffers/juce_AudioChannelSet.cpp" #include "buffers/juce_AudioProcessLoadMeasurer.cpp" -// #include "utilities/juce_IIRFilter.cpp" -// #include "utilities/juce_LagrangeInterpolator.cpp" -// #include "utilities/juce_WindowedSincInterpolator.cpp" -// #include "utilities/juce_Interpolators.cpp" -// #include "utilities/juce_SmoothedValue.cpp" +#include "utilities/juce_IIRFilter.cpp" +#include "utilities/juce_LagrangeInterpolator.cpp" +#include "utilities/juce_WindowedSincInterpolator.cpp" +#include "utilities/juce_Interpolators.cpp" +#include "utilities/juce_SmoothedValue.cpp" #include "midi/juce_MidiBuffer.cpp" -// #include "midi/juce_MidiFile.cpp" -// #include "midi/juce_MidiKeyboardState.cpp" +#include "midi/juce_MidiFile.cpp" +#include "midi/juce_MidiKeyboardState.cpp" #include "midi/juce_MidiMessage.cpp" -// #include "midi/juce_MidiMessageSequence.cpp" -// #include "midi/juce_MidiRPN.cpp" -// #include "mpe/juce_MPEValue.cpp" -// #include "mpe/juce_MPENote.cpp" -// #include "mpe/juce_MPEZoneLayout.cpp" -// #include "mpe/juce_MPEInstrument.cpp" -// #include "mpe/juce_MPEMessages.cpp" -// #include "mpe/juce_MPESynthesiserBase.cpp" -// #include "mpe/juce_MPESynthesiserVoice.cpp" -// #include "mpe/juce_MPESynthesiser.cpp" -// #include "mpe/juce_MPEUtils.cpp" -// #include "sources/juce_BufferingAudioSource.cpp" -// #include "sources/juce_ChannelRemappingAudioSource.cpp" -// #include "sources/juce_IIRFilterAudioSource.cpp" -// #include "sources/juce_MemoryAudioSource.cpp" -// #include "sources/juce_MixerAudioSource.cpp" -// #include "sources/juce_ResamplingAudioSource.cpp" -// #include "sources/juce_ReverbAudioSource.cpp" -// #include "sources/juce_ToneGeneratorAudioSource.cpp" -// #include "synthesisers/juce_Synthesiser.cpp" - +#include "midi/juce_MidiMessageSequence.cpp" +#include "midi/juce_MidiRPN.cpp" +#include "mpe/juce_MPEValue.cpp" +#include "mpe/juce_MPENote.cpp" +#include "mpe/juce_MPEZoneLayout.cpp" +#include "mpe/juce_MPEInstrument.cpp" +#include "mpe/juce_MPEMessages.cpp" +#include "mpe/juce_MPESynthesiserBase.cpp" +#include "mpe/juce_MPESynthesiserVoice.cpp" +#include "mpe/juce_MPESynthesiser.cpp" +#include "mpe/juce_MPEUtils.cpp" +#include "sources/juce_BufferingAudioSource.cpp" +#include "sources/juce_ChannelRemappingAudioSource.cpp" +#include "sources/juce_IIRFilterAudioSource.cpp" +#include "sources/juce_MemoryAudioSource.cpp" +#include "sources/juce_MixerAudioSource.cpp" +#include "sources/juce_ResamplingAudioSource.cpp" +#include "sources/juce_ReverbAudioSource.cpp" +#include "sources/juce_ToneGeneratorAudioSource.cpp" +#include "synthesisers/juce_Synthesiser.cpp" + #include "midi/ump/juce_UMP.h" #include "midi/ump/juce_UMPUtils.cpp" #include "midi/ump/juce_UMPView.cpp" #include "midi/ump/juce_UMPSysEx7.cpp" #include "midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp" - + #if JUCE_UNIT_TESTS #include "utilities/juce_ADSR_test.cpp" #include "midi/ump/juce_UMP_test.cpp" diff --git a/source/modules/juce_audio_basics/juce_audio_basics.h b/source/modules/juce_audio_basics/juce_audio_basics.h index 62ccbc38c..5a7ad2f57 100644 --- a/source/modules/juce_audio_basics/juce_audio_basics.h +++ b/source/modules/juce_audio_basics/juce_audio_basics.h @@ -32,7 +32,7 @@ ID: juce_audio_basics vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE audio and MIDI data classes description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. website: http://www.juce.com/juce @@ -120,4 +120,4 @@ #include "sources/juce_ReverbAudioSource.h" #include "sources/juce_ToneGeneratorAudioSource.h" #include "synthesisers/juce_Synthesiser.h" -#include "audio_play_head/juce_AudioPlayHead.h" \ No newline at end of file +#include "audio_play_head/juce_AudioPlayHead.h" diff --git a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp index cdbcd122f..b0593bb34 100644 --- a/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp +++ b/source/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp @@ -91,9 +91,9 @@ void BufferingAudioSource::releaseResources() buffer.setSize (numberOfChannels, 0); - // MSVC2015 seems to need this if statement to not generate a warning during linking. + // MSVC2017 seems to need this if statement to not generate a warning during linking. // As source is set in the constructor, there is no way that source could - // ever equal this, but it seems to make MSVC2015 happy. + // ever equal this, but it seems to make MSVC2017 happy. if (source != this) source->releaseResources(); } diff --git a/source/modules/juce_audio_devices/juce_audio_devices.cpp b/source/modules/juce_audio_devices/juce_audio_devices.cpp index 74838be94..f4b4de068 100644 --- a/source/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/source/modules/juce_audio_devices/juce_audio_devices.cpp @@ -238,5 +238,5 @@ namespace juce #include "audio_io/juce_AudioIODeviceType.cpp" #include "midi_io/juce_MidiMessageCollector.cpp" #include "midi_io/juce_MidiDevices.cpp" -// #include "sources/juce_AudioSourcePlayer.cpp" -// #include "sources/juce_AudioTransportSource.cpp" +#include "sources/juce_AudioSourcePlayer.cpp" +#include "sources/juce_AudioTransportSource.cpp" diff --git a/source/modules/juce_audio_devices/juce_audio_devices.h b/source/modules/juce_audio_devices/juce_audio_devices.h index 567322216..1e80fb206 100644 --- a/source/modules/juce_audio_devices/juce_audio_devices.h +++ b/source/modules/juce_audio_devices/juce_audio_devices.h @@ -32,7 +32,7 @@ ID: juce_audio_devices vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE audio and MIDI I/O device classes description: Classes to play and record from audio and MIDI I/O devices website: http://www.juce.com/juce diff --git a/source/modules/juce_audio_devices/native/juce_android_Audio.cpp b/source/modules/juce_audio_devices/native/juce_android_Audio.cpp deleted file mode 100644 index 75607c691..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ /dev/null @@ -1,470 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ - STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \ - METHOD (constructor, "", "(IIIIII)V") \ - METHOD (getState, "getState", "()I") \ - METHOD (play, "play", "()V") \ - METHOD (stop, "stop", "()V") \ - METHOD (release, "release", "()V") \ - METHOD (flush, "flush", "()V") \ - METHOD (write, "write", "([SII)I") \ - -DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack") -#undef JNI_CLASS_MEMBERS - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ - METHOD (constructor, "", "(IIIII)V") \ - METHOD (getState, "getState", "()I") \ - METHOD (startRecording, "startRecording", "()V") \ - METHOD (stop, "stop", "()V") \ - METHOD (read, "read", "([SII)I") \ - METHOD (release, "release", "()V") \ - -DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord") -#undef JNI_CLASS_MEMBERS - -//============================================================================== -enum -{ - CHANNEL_OUT_STEREO = 12, - CHANNEL_IN_STEREO = 12, - CHANNEL_IN_MONO = 16, - ENCODING_PCM_16BIT = 2, - STREAM_MUSIC = 3, - MODE_STREAM = 1, - STATE_UNINITIALIZED = 0 -}; - -const char* const javaAudioTypeName = "Android Audio"; - -//============================================================================== -class AndroidAudioIODevice : public AudioIODevice, - public Thread -{ -public: - //============================================================================== - AndroidAudioIODevice (const String& deviceName) - : AudioIODevice (deviceName, javaAudioTypeName), - Thread ("audio"), - minBufferSizeOut (0), minBufferSizeIn (0), callback (nullptr), sampleRate (0), - numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2), - numClientOutputChannels (0), numDeviceOutputChannels (0), - actualBufferSize (0), isRunning (false), - inputChannelBuffer (1, 1), - outputChannelBuffer (1, 1) - { - JNIEnv* env = getEnv(); - sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM); - - minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT); - minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT); - - if (minBufferSizeIn <= 0) - { - minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT); - - if (minBufferSizeIn > 0) - numDeviceInputChannelsAvailable = 1; - else - numDeviceInputChannelsAvailable = 0; - } - - DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; " - << sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable); - } - - ~AndroidAudioIODevice() override - { - close(); - } - - StringArray getOutputChannelNames() override - { - StringArray s; - s.add ("Left"); - s.add ("Right"); - return s; - } - - StringArray getInputChannelNames() override - { - StringArray s; - - if (numDeviceInputChannelsAvailable == 2) - { - s.add ("Left"); - s.add ("Right"); - } - else if (numDeviceInputChannelsAvailable == 1) - { - s.add ("Audio Input"); - } - - return s; - } - - Array getAvailableSampleRates() override - { - Array r; - r.add ((double) sampleRate); - return r; - } - - Array getAvailableBufferSizes() override - { - Array b; - int n = 16; - - for (int i = 0; i < 50; ++i) - { - b.add (n); - n += n < 64 ? 16 - : (n < 512 ? 32 - : (n < 1024 ? 64 - : (n < 2048 ? 128 : 256))); - } - - return b; - } - - int getDefaultBufferSize() override { return 2048; } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double requestedSampleRate, - int bufferSize) override - { - close(); - - if (sampleRate != (int) requestedSampleRate) - return "Sample rate not allowed"; - - lastError.clear(); - int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; - - numDeviceInputChannels = 0; - numDeviceOutputChannels = 0; - - activeOutputChans = outputChannels; - activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); - numClientOutputChannels = activeOutputChans.countNumberOfSetBits(); - - activeInputChans = inputChannels; - activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); - numClientInputChannels = activeInputChans.countNumberOfSetBits(); - - actualBufferSize = preferredBufferSize; - inputChannelBuffer.setSize (2, actualBufferSize); - inputChannelBuffer.clear(); - outputChannelBuffer.setSize (2, actualBufferSize); - outputChannelBuffer.clear(); - - JNIEnv* env = getEnv(); - - if (numClientOutputChannels > 0) - { - numDeviceOutputChannels = 2; - 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 = (getAndroidSDKVersion() >= 24); - getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : nullptr; - - int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState); - if (outputDeviceState > 0) - { - isRunning = true; - } - else - { - // failed to open the device - outputDevice.clear(); - lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState); - } - } - - if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) - { - if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) - { - // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio - // before trying to open an audio input device. This is not going to work! - jassertfalse; - - inputDevice.clear(); - lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO"; - } - else - { - numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); - 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) - { - isRunning = true; - } - else - { - // failed to open the device - inputDevice.clear(); - lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState); - } - } - } - - if (isRunning) - { - if (outputDevice != nullptr) - env->CallVoidMethod (outputDevice, AudioTrack.play); - - if (inputDevice != nullptr) - env->CallVoidMethod (inputDevice, AudioRecord.startRecording); - - startThread (8); - } - else - { - closeDevices(); - } - - return lastError; - } - - void close() override - { - if (isRunning) - { - stopThread (2000); - isRunning = false; - closeDevices(); - } - } - - int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; } - int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; } - bool isOpen() override { return isRunning; } - int getCurrentBufferSizeSamples() override { return actualBufferSize; } - int getCurrentBitDepth() override { return 16; } - double getCurrentSampleRate() override { return sampleRate; } - BigInteger getActiveOutputChannels() const override { return activeOutputChans; } - BigInteger getActiveInputChannels() const override { return activeInputChans; } - String getLastError() override { return lastError; } - bool isPlaying() override { return isRunning && callback != nullptr; } - - int getXRunCount() const noexcept override - { - if (outputDevice != nullptr && getUnderrunCount != nullptr) - return getEnv()->CallIntMethod (outputDevice, getUnderrunCount); - - return -1; - } - - void start (AudioIODeviceCallback* newCallback) override - { - if (isRunning && callback != newCallback) - { - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (this); - - const ScopedLock sl (callbackLock); - callback = newCallback; - } - } - - void stop() override - { - if (isRunning) - { - AudioIODeviceCallback* lastCallback; - - { - const ScopedLock sl (callbackLock); - lastCallback = callback; - callback = nullptr; - } - - if (lastCallback != nullptr) - lastCallback->audioDeviceStopped(); - } - } - - void run() override - { - JNIEnv* env = getEnv(); - jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels)); - - using NativeInt16 = AudioData::Format; - using NativeFloat32 = AudioData::Format; - - while (! threadShouldExit()) - { - if (inputDevice != nullptr) - { - jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels); - - if (numRead < actualBufferSize * numDeviceInputChannels) - { - DBG ("Audio read under-run! " << numRead); - } - - jshort* const src = env->GetShortArrayElements (audioBuffer, nullptr); - - AudioData::deinterleaveSamples (AudioData::InterleavedSource { reinterpret_cast (src), numDeviceInputChannels }, - AudioData::NonInterleavedDest { inputChannelBuffer.getArrayOfWritePointers(), inputChannelBuffer.getNumChannels() }, - actualBufferSize); - - env->ReleaseShortArrayElements (audioBuffer, src, 0); - } - - if (threadShouldExit()) - break; - - { - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - { - callback->audioDeviceIOCallbackWithContext (inputChannelBuffer.getArrayOfReadPointers(), - numClientInputChannels, - outputChannelBuffer.getArrayOfWritePointers(), - numClientOutputChannels, - actualBufferSize, {}); - } - else - { - outputChannelBuffer.clear(); - } - } - - if (outputDevice != nullptr) - { - if (threadShouldExit()) - break; - - jshort* const dest = env->GetShortArrayElements (audioBuffer, nullptr); - - AudioData::interleaveSamples (AudioData::NonInterleavedSource { outputChannelBuffer.getArrayOfReadPointers(), outputChannelBuffer.getNumChannels() }, - AudioData::InterleavedDest { reinterpret_cast (dest), numDeviceOutputChannels }, - actualBufferSize); - - env->ReleaseShortArrayElements (audioBuffer, dest, 0); - jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels); - - if (numWritten < actualBufferSize * numDeviceOutputChannels) - { - DBG ("Audio write underrun! " << numWritten); - } - } - } - } - - int minBufferSizeOut, minBufferSizeIn; - -private: - //============================================================================== - CriticalSection callbackLock; - AudioIODeviceCallback* callback; - jint sampleRate; - int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable; - int numClientOutputChannels, numDeviceOutputChannels; - int actualBufferSize; - bool isRunning; - String lastError; - BigInteger activeOutputChans, activeInputChans; - GlobalRef outputDevice, inputDevice; - AudioBuffer inputChannelBuffer, outputChannelBuffer; - jmethodID getUnderrunCount = nullptr; - - void closeDevices() - { - if (outputDevice != nullptr) - { - outputDevice.callVoidMethod (AudioTrack.stop); - outputDevice.callVoidMethod (AudioTrack.release); - outputDevice.clear(); - } - - if (inputDevice != nullptr) - { - inputDevice.callVoidMethod (AudioRecord.stop); - inputDevice.callVoidMethod (AudioRecord.release); - inputDevice.clear(); - } - } - - JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice) -}; - -//============================================================================== -class AndroidAudioIODeviceType : public AudioIODeviceType -{ -public: - AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {} - - //============================================================================== - void scanForDevices() {} - StringArray getDeviceNames (bool) const { return StringArray (javaAudioTypeName); } - int getDefaultDeviceIndex (bool) const { return 0; } - int getIndexOfDevice (AudioIODevice* device, bool) const { return device != nullptr ? 0 : -1; } - bool hasSeparateInputsAndOutputs() const { return false; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) - { - std::unique_ptr dev; - - if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) - { - dev.reset (new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName - : inputDeviceName)); - - if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0) - dev = nullptr; - } - - return dev.release(); - } - -private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType) -}; - - -//============================================================================== -extern bool isOboeAvailable(); -extern bool isOpenSLAvailable(); - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h b/source/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h deleted file mode 100644 index c76f96d6e..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_HighPerformanceAudioHelpers.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -//============================================================================== -/** - Some shared helpers methods for using the high-performance audio paths on - Android devices (OpenSL and Oboe). - - @tags{Audio} -*/ -namespace AndroidHighPerformanceAudioHelpers -{ - //============================================================================== - static double getNativeSampleRate() - { - return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); - } - - static int getNativeBufferSizeHint() - { - // This property is a hint of a native buffer size but it does not guarantee the size used. - auto deviceBufferSize = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue(); - - if (deviceBufferSize == 0) - return 192; - - return deviceBufferSize; - } - - static bool isProAudioDevice() - { - static bool isSapaSupported = SystemStats::getDeviceManufacturer().containsIgnoreCase ("SAMSUNG") - && DynamicLibrary().open ("libapa_jni.so"); - - return androidHasSystemFeature ("android.hardware.audio.pro") || isSapaSupported; - } - - static bool hasLowLatencyAudioPath() - { - return androidHasSystemFeature ("android.hardware.audio.low_latency"); - } - - static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate) - { - return ((requestedBufferSize % nativeBufferSize) == 0) - && (requestedSampleRate == getNativeSampleRate()) - && isProAudioDevice(); - } - - //============================================================================== - static int getMinimumBuffersToEnqueue (int nativeBufferSize, double requestedSampleRate) - { - if (canUseHighPerformanceAudioPath (nativeBufferSize, nativeBufferSize, (int) requestedSampleRate)) - { - // see https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes.html#sandp - // "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." - return (getAndroidSDKVersion() >= 18 ? 1 : 2); - } - - // not using low-latency path so we can use the absolute minimum number of buffers to queue - return 1; - } - - static int buffersToQueueForBufferDuration (int nativeBufferSize, int bufferDurationInMs, double sampleRate) noexcept - { - auto maxBufferFrames = static_cast (std::ceil (bufferDurationInMs * sampleRate / 1000.0)); - auto maxNumBuffers = static_cast (std::ceil (static_cast (maxBufferFrames) - / static_cast (nativeBufferSize))); - - return jmax (getMinimumBuffersToEnqueue (nativeBufferSize, sampleRate), maxNumBuffers); - } - - static int getMaximumBuffersToEnqueue (int nativeBufferSize, double maximumSampleRate) noexcept - { - static constexpr int maxBufferSizeMs = 200; - - return jmax (8, buffersToQueueForBufferDuration (nativeBufferSize, maxBufferSizeMs, maximumSampleRate)); - } - - static Array getAvailableBufferSizes (int nativeBufferSize, Array availableSampleRates) - { - auto minBuffersToQueue = getMinimumBuffersToEnqueue (nativeBufferSize, getNativeSampleRate()); - auto maxBuffersToQueue = getMaximumBuffersToEnqueue (nativeBufferSize, findMaximum (availableSampleRates.getRawDataPointer(), - availableSampleRates.size())); - - Array bufferSizes; - - for (int i = minBuffersToQueue; i <= maxBuffersToQueue; ++i) - bufferSizes.add (i * nativeBufferSize); - - return bufferSizes; - } - - static int getDefaultBufferSize (int nativeBufferSize, double currentSampleRate) - { - static constexpr int defaultBufferSizeForLowLatencyDeviceMs = 40; - static constexpr int defaultBufferSizeForStandardLatencyDeviceMs = 100; - - auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs - : defaultBufferSizeForStandardLatencyDeviceMs); - - auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (nativeBufferSize, defaultBufferLength, currentSampleRate); - return defaultBuffersToEnqueue * nativeBufferSize; - } -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp b/source/modules/juce_audio_devices/native/juce_android_Midi.cpp deleted file mode 100644 index 371ac4c8e..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ /dev/null @@ -1,701 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -//============================================================================== -// This byte-code is generated from native/java/com/rmsl/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,43,113,161,94,0,3,106,97,118,97,77,105,100,105,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,11,124,220, -69,181,255,153,223,99,119,179,217,36,155,77,218,164,105,178,217,164,73,179,165,205,171,233,35,109,146,182,121,180,77,218,164,45, -201,182,72,195,5,183,201,182,217,146,236,134,236,166,180,114,189,20,244,210,162,168,40,80,65,177,162,2,242,18,81,65,80,17,81, -80,81,81,122,149,63,214,39,138,112,69,69,64,20,17,229,218,255,247,204,204,110,126,109,3,213,246,243,221,51,191,51,103,206,204, -156,57,115,230,204,111,183,29,141,237,247,54,181,44,167,234,195,55,252,236,231,159,31,184,160,244,71,71,143,188,248,212,248, -199,142,188,62,189,243,225,179,11,151,53,157,77,52,73,68,251,119,44,11,144,254,243,246,109,68,231,10,197,95,15,60,105,19,157,3, -250,188,139,40,4,250,134,151,232,179,76,115,137,114,64,211,133,68,55,174,33,186,22,26,254,86,79,244,119,224,255,0,163,129,200, -6,22,3,13,64,43,176,14,232,1,54,1,219,128,93,192,81,224,105,224,31,192,63,1,163,145,200,13,132,129,173,192,32,240,54,224,66, -224,82,224,125,192,167,128,91,129,59,128,207,2,247,2,95,6,30,2,30,1,190,3,188,4,20,55,17,173,4,118,1,215,0,15,3,127,0,252,205, -68,109,192,249,192,101,192,221,192,143,128,23,129,130,165,68,29,192,46,224,74,224,51,192,47,129,146,22,162,85,192,249,192,101, -192,17,224,46,224,155,192,79,128,63,2,198,50,216,14,120,47,240,16,240,10,16,90,78,148,0,238,5,126,11,204,89,65,180,2,216,9,188, -3,248,24,240,32,112,28,120,9,48,86,162,47,96,49,176,22,216,1,164,129,107,128,219,129,135,1,187,149,168,9,232,1,222,6,76,0,151, -1,71,128,187,128,175,2,79,0,214,42,244,7,132,129,181,192,32,112,29,112,59,112,63,240,75,224,215,192,115,192,239,129,151,129,191, -1,111,0,98,53,214,1,200,7,138,128,82,32,8,212,0,139,129,165,192,10,96,21,208,1,116,2,235,129,56,112,61,240,16,240,35,224,121, -224,85,64,180,17,121,129,2,160,20,88,8,180,0,107,129,141,192,185,192,52,112,37,240,81,224,115,192,35,192,15,128,227,192,239, -129,87,128,215,1,119,59,244,0,149,192,66,160,30,88,1,116,3,3,192,78,96,4,184,8,216,15,92,2,28,2,62,0,220,0,124,10,248,60,240,53, -224,7,192,79,129,231,128,63,1,175,3,86,7,244,3,107,129,62,96,28,120,31,112,45,240,73,224,110,224,94,224,107,192,163,192,49,224, -23,192,235,192,92,236,133,122,160,11,56,15,72,1,239,4,174,6,62,10,220,9,220,15,60,12,252,1,120,5,120,29,48,214,98,46,192,86, -224,0,112,45,112,23,112,31,240,13,224,127,128,223,0,127,4,222,0,242,214,97,254,64,24,88,9,172,3,54,1,219,128,97,224,124,96,4, -136,3,147,192,37,192,97,224,58,224,40,240,105,224,94,224,33,224,49,224,41,224,103,192,111,128,151,128,191,1,255,4,188,157,68, -21,64,61,176,26,88,3,116,3,189,192,22,96,59,112,1,16,3,246,2,83,192,21,192,199,129,251,128,199,129,231,128,215,0,111,23,209,60, -96,17,176,14,56,27,24,3,46,2,46,5,62,8,220,9,124,9,248,54,240,4,240,28,240,119,192,213,13,95,6,26,128,94,224,60,96,28,184,24, -184,12,184,10,184,6,248,56,112,43,240,21,224,199,192,203,192,95,129,55,0,119,15,81,33,48,31,88,12,44,7,122,128,45,192,249,64, -12,216,11,92,14,188,7,56,2,124,10,120,8,248,22,240,4,240,99,224,23,192,51,192,171,128,23,65,178,8,168,0,106,129,197,192,70,224, -108,96,23,144,4,46,5,174,2,62,4,220,0,220,12,124,1,248,10,240,117,224,123,192,83,192,207,129,103,129,151,128,191,1,198,6,216, -11,88,6,108,1,182,3,5,136,185,197,64,53,176,0,168,1,106,129,133,64,29,16,6,22,1,103,1,139,129,37,0,194,49,33,180,18,66,34,33, -252,17,194,28,33,164,17,66,22,33,68,17,194,18,33,244,16,66,11,33,108,16,182,63,97,203,18,182,26,97,59,16,220,154,224,114,132, -37,36,44,5,193,148,212,163,207,7,12,137,54,2,189,64,31,176,9,216,12,244,3,3,192,22,96,43,128,99,133,112,220,208,32,48,4,68,128, -29,192,219,128,97,224,63,128,11,248,252,1,118,1,163,64,12,216,13,140,3,255,9,92,10,28,4,46,3,46,7,222,5,188,155,148,77,50,127, -252,154,78,98,226,133,186,188,31,229,50,80,67,63,115,217,212,229,74,93,158,212,50,150,230,87,233,242,65,205,247,56,228,113,4, -210,85,154,159,171,249,243,129,60,224,90,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216, -88,38,168,101,42,117,121,82,151,25,55,106,153,106,45,83,161,203,55,47,81,178,92,190,75,203,215,56,218,214,234,182,220,15,251, -208,3,122,12,13,142,113,54,58,198,214,228,24,27,151,31,94,162,242,2,46,63,182,100,134,159,177,103,179,67,79,179,99,252,92,62, -230,40,103,230,184,204,209,87,171,163,47,246,201,227,154,191,90,243,217,47,58,116,121,66,151,185,109,66,151,127,133,114,82,151, -159,95,162,114,26,46,255,5,229,139,116,217,194,230,216,175,203,62,148,167,116,185,20,229,148,46,135,80,222,167,203,75,80,190, -88,151,151,57,202,235,234,103,116,246,59,202,55,58,250,138,56,248,231,57,250,29,117,240,39,29,229,253,142,126,15,58,248,135,29, -109,175,70,249,64,166,47,135,252,109,40,191,67,151,239,113,180,61,230,24,15,175,93,70,254,49,7,127,210,81,126,208,209,215,163, -40,79,103,244,160,124,137,46,31,119,216,234,87,40,167,117,249,133,122,181,111,215,232,53,122,167,46,243,26,253,151,46,179,253, -51,229,135,29,252,140,255,116,234,182,92,238,114,248,67,183,195,31,122,52,127,190,46,95,43,125,190,137,238,39,69,215,10,110, -83,64,87,201,182,205,244,1,73,87,210,135,36,245,80,135,96,31,46,165,247,242,90,163,247,231,37,21,244,71,73,107,169,74,214,47,164, -197,130,227,66,177,148,171,210,252,42,205,95,160,159,153,110,19,188,199,44,250,48,49,245,211,95,36,85,245,53,186,190,86,143,167, -22,145,247,136,164,93,116,167,164,37,244,138,164,203,232,53,93,95,46,20,13,10,181,71,111,39,166,107,232,247,164,227,190,224, -216,95,73,31,228,50,36,95,37,142,117,30,122,84,82,147,190,37,169,77,63,37,142,117,110,250,184,164,213,244,85,77,159,228,117, -192,137,241,49,77,63,43,169,69,223,150,116,11,45,135,126,27,124,55,113,28,236,165,62,193,116,5,13,8,190,3,40,190,55,75,189,116, -189,164,57,180,30,245,62,173,39,79,215,231,129,115,189,164,185,212,45,20,237,17,28,35,243,232,235,196,180,138,126,70,28,199, -213,120,252,136,164,63,144,180,128,74,4,83,63,205,23,28,219,213,184,57,198,63,165,233,207,73,197,215,239,75,58,72,199,37,45,164, -159,104,62,215,23,107,189,197,56,165,214,65,207,28,61,174,18,156,74,223,145,180,137,230,8,166,171,105,174,164,29,212,44,105, -59,237,16,28,167,85,251,82,216,255,168,166,108,175,121,90,79,25,198,255,32,113,60,13,208,151,136,227,176,65,183,72,63,92,47,235, -217,239,20,21,244,136,164,181,244,61,73,183,211,15,37,221,72,66,250,235,98,42,148,116,9,5,36,61,155,106,36,221,68,155,36,221, -64,219,165,95,174,147,250,66,122,92,76,239,149,84,217,39,132,72,254,11,73,7,232,15,186,62,79,182,235,167,34,73,55,83,151,80,252, -94,77,251,165,95,175,149,122,171,180,222,42,173,183,74,235,173,210,250,170,116,251,42,221,190,74,183,175,214,237,170,181,124, -181,150,175,214,242,213,90,190,90,203,47,192,78,231,254,22,32,43,49,228,243,50,50,53,181,36,93,74,182,164,203,201,165,169,91, -243,243,53,45,144,180,153,252,154,22,203,253,214,37,245,214,160,255,143,72,90,77,223,144,212,69,223,37,117,22,62,46,233,89, -180,90,238,51,181,62,181,122,190,181,240,148,251,36,157,71,95,148,116,33,61,36,169,90,191,90,248,205,99,146,238,160,39,36,221,78, -199,52,253,31,73,139,232,71,146,214,208,255,147,116,62,253,88,210,85,228,145,253,181,82,142,166,94,161,248,185,146,182,145,79, -168,120,80,42,233,92,154,39,105,41,149,73,186,149,170,37,109,164,5,146,118,83,139,164,27,40,34,227,68,189,156,199,66,100,94, -247,232,56,241,180,140,15,103,97,230,138,186,37,157,67,95,147,180,140,30,38,62,235,23,75,126,163,150,135,118,26,18,76,43,232, -109,130,207,118,213,174,73,219,167,9,158,254,77,226,51,92,245,211,12,59,255,142,56,183,236,145,114,45,240,124,222,15,203,116, -187,101,144,59,172,159,111,212,207,55,73,90,71,47,232,231,165,66,229,1,27,37,141,208,160,224,28,53,76,239,35,206,83,149,158, -21,186,253,10,200,127,66,210,74,217,207,10,100,191,47,75,26,162,38,161,248,172,111,165,110,183,82,247,191,82,247,179,82,247,179, -82,247,211,138,241,255,146,152,6,233,159,196,121,135,26,215,106,77,219,180,158,54,100,187,107,4,231,199,234,185,93,251,23,159, -77,96,203,119,35,36,227,2,206,50,36,226,55,32,17,62,178,69,229,97,194,53,147,71,113,253,213,168,127,98,139,122,14,233,246,204, -127,251,18,69,111,66,253,31,116,125,149,174,111,114,212,63,128,250,186,173,170,126,129,214,107,59,244,31,67,253,144,174,175,209, -252,118,71,253,175,80,255,30,93,95,171,245,207,1,198,180,254,151,81,255,57,93,191,80,183,115,142,127,29,228,22,109,83,207, -117,142,241,101,234,183,161,190,91,215,115,14,30,197,197,96,108,64,201,165,52,189,124,96,166,238,26,71,249,227,186,254,14,7, -239,11,186,252,16,232,55,29,229,99,3,42,151,103,153,159,1,255,171,219,254,73,83,99,139,162,69,154,134,53,237,208,52,162,105,108, -203,76,95,251,53,239,93,91,88,183,33,203,231,111,80,247,140,73,127,30,158,171,225,59,147,254,79,226,121,216,111,33,234,15,251, -13,26,14,24,56,183,88,158,245,36,55,168,123,66,4,53,23,249,175,32,62,21,19,161,113,172,181,87,222,13,44,45,183,111,131,186,67, -92,36,123,241,137,68,200,192,126,130,172,223,150,207,124,30,152,168,99,217,119,109,80,103,94,36,100,81,164,202,130,204,45,168, -241,138,5,184,224,38,66,183,98,124,62,248,98,143,148,177,101,22,128,188,17,109,230,130,78,249,111,71,159,62,49,229,255,52,183, -49,90,141,60,240,110,67,153,219,248,40,16,72,52,173,130,39,133,95,206,215,35,35,58,186,65,217,129,239,53,46,57,51,220,179,55, -168,123,99,160,112,105,177,77,129,170,150,226,66,140,163,16,253,249,176,127,114,41,210,204,227,226,91,148,207,72,132,62,5,223, -13,116,182,20,87,34,126,205,161,50,227,66,186,40,212,12,222,76,139,192,41,45,110,150,181,150,182,69,59,34,105,190,156,11,247, -253,205,13,234,94,227,180,85,39,180,32,122,106,253,95,208,250,3,162,64,68,154,149,229,133,148,252,79,105,169,240,171,94,104,98, -237,79,109,80,239,56,3,37,1,151,214,7,61,94,42,179,160,199,206,147,122,34,232,59,33,47,151,62,177,74,100,234,124,186,46,252, -74,107,206,114,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149,8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101, -230,207,193,45,215,103,5,106,185,20,161,5,233,117,232,161,0,173,125,246,128,109,185,46,242,127,68,181,247,23,161,149,207,78, -172,203,165,206,255,14,127,53,17,242,225,6,28,254,18,101,253,203,187,81,221,73,79,246,175,75,225,95,249,200,211,92,202,231,55, -170,123,232,164,191,1,109,134,23,228,208,112,141,139,134,107,189,180,115,161,7,150,63,47,228,150,107,107,75,255,18,84,183,81, -197,146,128,25,233,116,81,171,112,19,211,132,127,49,234,34,157,57,224,228,72,26,233,242,162,175,255,130,157,135,187,161,179,219, -5,45,121,122,5,2,122,5,194,207,171,120,196,186,133,168,199,241,37,228,152,218,209,7,199,206,132,159,51,254,164,255,42,237,95, -25,31,239,217,168,226,104,36,132,126,170,184,159,41,233,215,133,50,142,8,233,151,155,54,170,189,26,128,229,132,230,109,219,56, -227,171,249,152,63,223,221,119,108,84,123,171,35,215,71,67,151,121,200,125,208,125,141,184,89,220,103,125,107,159,167,83,203, -90,250,246,191,219,209,222,208,99,153,218,168,98,98,196,159,163,60,213,15,171,64,98,187,223,45,125,133,159,19,161,37,24,95,192, -127,158,223,125,82,219,75,222,162,109,107,182,109,61,183,165,76,91,30,11,143,225,221,122,109,39,253,124,235,24,22,62,26,54,114, -105,24,222,148,159,93,171,107,29,107,149,171,215,42,23,86,93,32,215,202,167,215,202,135,181,202,203,174,21,244,116,231,254, -27,107,117,123,118,173,250,102,93,171,207,101,215,10,253,84,229,205,186,86,247,103,214,10,158,232,214,51,252,42,120,69,220, -174,89,143,28,52,177,174,134,58,99,51,99,235,20,122,108,175,169,119,50,122,108,222,204,122,63,233,88,175,12,239,167,14,158,41,71, -137,115,110,163,122,143,51,44,10,96,79,182,234,176,145,47,227,180,122,27,244,130,163,77,198,23,254,58,11,207,232,117,198,66, -75,206,41,191,87,197,227,64,168,197,46,128,15,36,224,179,38,172,193,81,195,195,251,23,113,231,157,242,54,51,163,167,162,247,116, -221,139,102,225,45,159,133,215,217,123,242,252,248,207,192,44,188,115,28,60,91,90,14,231,90,47,71,8,182,67,17,236,240,25,105, -7,156,91,102,33,13,91,60,66,75,90,209,164,169,94,245,30,168,220,168,161,10,35,32,134,155,3,88,249,187,81,3,159,109,246,99,189, -10,36,77,192,103,133,46,113,180,97,73,63,158,11,33,225,149,52,225,47,213,252,66,10,26,184,235,137,160,81,39,242,68,248,53,158, -205,60,212,85,202,241,153,50,255,112,73,159,90,116,105,253,146,69,186,108,208,7,122,85,78,90,110,98,44,102,100,16,186,141,5, -196,52,225,159,199,49,83,36,252,213,242,52,11,44,107,233,154,11,110,21,71,123,163,204,186,6,190,216,136,8,204,103,155,137,253, -230,201,158,48,65,179,16,40,195,242,133,223,200,67,169,206,80,249,195,66,180,172,215,190,36,112,255,202,248,239,103,122,85,125, -196,239,207,250,53,215,124,161,55,115,190,171,209,200,248,235,87,111,10,213,249,174,246,233,151,122,249,30,137,57,8,204,65,68, -154,2,24,77,30,69,154,10,249,14,64,252,28,89,10,13,161,125,136,220,65,193,254,31,20,117,164,250,44,146,125,21,99,223,168,179, -252,209,94,245,14,53,96,77,250,107,97,185,225,72,49,13,239,40,134,69,138,169,204,252,51,180,148,224,70,228,51,42,141,18,26,30, -44,1,191,132,86,225,124,42,51,176,163,204,49,185,219,113,115,194,153,181,2,62,240,81,62,19,6,231,225,105,25,158,62,36,159,74, -79,170,43,59,233,105,142,212,151,240,47,100,203,83,149,25,48,150,47,245,210,6,76,51,17,74,35,214,29,55,12,172,173,148,105,90, -68,131,86,248,113,143,62,171,254,212,171,114,207,200,72,41,218,31,225,157,97,113,30,98,145,215,108,53,155,100,30,98,201,113,7, -41,130,160,86,105,250,165,77,205,153,21,54,3,203,91,134,126,123,66,175,176,89,102,171,21,30,202,174,240,79,78,232,21,54,19,161, -247,227,172,101,205,79,156,40,52,2,70,248,159,46,61,142,185,125,234,253,120,164,171,12,250,63,206,243,48,166,252,119,104,250, -105,80,110,53,95,142,199,144,154,27,41,210,13,217,208,81,57,150,74,172,97,194,63,34,71,192,189,12,73,249,103,79,20,138,128,8, -255,147,227,140,242,158,230,62,245,174,189,220,174,165,10,59,146,230,89,95,207,179,181,22,116,117,241,89,144,86,118,192,156,93, -66,102,100,46,212,181,90,115,101,207,46,104,175,52,131,116,28,241,46,130,43,81,165,165,172,193,121,193,128,101,8,33,194,191,13, -218,133,70,158,21,180,235,44,246,139,6,217,107,163,244,19,254,59,214,167,222,209,151,91,232,31,190,50,193,185,150,57,60,52, -151,130,214,204,106,39,66,23,82,140,103,162,86,196,228,113,152,114,28,65,57,14,53,227,133,50,170,207,145,185,203,53,216,81,9, -255,229,188,30,214,69,254,43,117,166,195,220,240,51,121,102,208,170,51,121,158,108,197,5,77,156,145,222,192,86,196,92,144,145, -154,60,78,142,24,43,179,123,108,5,185,244,110,186,70,175,75,185,129,241,26,145,78,101,29,49,51,38,209,42,74,51,99,194,170,4, -229,109,183,82,40,187,200,252,206,95,41,243,187,150,179,159,63,17,52,56,222,4,40,252,127,42,226,168,189,84,39,123,10,19,231,157, -220,239,93,125,234,59,136,114,47,250,244,182,250,139,41,48,47,209,148,162,27,243,124,88,211,45,20,249,250,60,204,224,99,200, -197,185,119,55,86,45,232,45,164,192,162,240,139,131,205,243,105,50,116,17,238,192,62,119,171,123,21,69,246,97,44,46,68,70,87,11, -164,90,225,175,131,205,21,104,59,31,121,183,15,121,118,14,110,11,33,114,127,221,122,118,159,139,223,144,182,98,6,172,189,218, -106,129,158,171,97,197,68,211,77,212,108,5,189,225,99,24,177,183,78,168,246,101,220,222,211,234,249,221,137,106,156,131,147,235, -122,233,225,214,240,51,65,47,102,246,32,201,251,124,55,102,196,223,189,168,44,104,163,244,63,94,255,122,48,249,253,98,192,21, -73,97,55,134,206,194,126,80,190,16,73,205,129,205,110,228,83,42,165,246,128,90,249,59,100,182,201,214,182,165,79,151,72,107,219, -210,3,26,149,44,246,64,145,92,77,222,3,187,32,31,254,157,90,243,128,57,140,250,32,116,207,59,85,163,99,151,151,57,118,249,66, -146,178,208,184,64,106,108,65,187,15,202,118,149,102,53,228,182,177,246,223,12,239,155,7,185,217,34,70,117,70,151,242,5,83,80, -43,34,6,211,74,211,146,39,147,233,120,114,201,167,76,84,41,101,221,63,83,122,175,69,153,179,26,206,190,56,143,89,4,251,45,211, -249,141,192,109,182,64,223,21,70,54,169,247,211,51,123,39,242,212,92,170,218,19,48,197,242,229,211,45,216,107,46,59,225,15,113, -244,241,84,93,9,238,178,229,215,214,32,50,122,144,199,7,101,244,106,249,205,92,170,246,46,204,156,16,185,129,162,150,53,240, -166,162,132,191,130,235,125,147,235,14,210,151,31,225,189,244,73,58,110,90,66,44,11,63,29,48,195,127,58,110,218,66,44,15,255,160, -208,176,245,222,190,114,147,218,43,129,80,185,192,138,194,243,63,192,86,49,86,25,33,120,83,28,62,193,62,135,155,153,63,200,39, -245,210,57,210,139,187,228,141,207,141,179,37,252,87,117,194,76,134,246,106,89,139,185,127,8,138,128,204,3,121,190,243,49,38, -158,77,142,180,67,69,54,167,61,186,73,249,93,192,63,25,74,200,155,99,97,182,238,230,76,93,104,166,46,160,199,124,199,38,245,253, -158,7,188,237,158,114,106,245,108,193,189,75,237,60,15,242,163,72,46,172,249,96,192,35,174,92,254,157,77,176,91,110,78,192, -208,59,218,195,109,6,243,230,83,203,241,85,196,54,134,230,188,170,95,6,60,203,159,111,166,13,86,158,135,45,140,249,187,90,246, -177,117,43,164,135,200,54,5,21,212,242,231,114,240,202,217,83,108,182,19,124,27,251,41,63,147,39,120,202,114,254,33,79,145,235, -81,95,105,55,243,14,183,3,117,225,123,143,123,60,34,124,236,184,39,71,136,43,195,247,7,189,101,72,142,195,127,206,243,96,111, -122,56,131,28,68,235,183,103,227,216,5,217,187,241,156,205,234,59,56,142,172,221,50,42,201,40,230,184,115,23,56,238,220,11,101, -44,133,55,24,45,131,47,159,96,107,241,25,98,105,159,59,107,179,186,187,241,249,199,185,87,160,185,197,111,194,79,35,56,227,3, -34,177,46,76,77,129,240,235,252,78,219,144,57,212,50,200,223,196,241,44,15,86,205,75,139,121,188,91,61,108,69,15,172,19,112,7, -114,248,198,23,241,149,225,182,248,30,62,99,242,217,47,94,128,239,173,242,157,47,123,129,156,47,176,166,229,133,38,142,132,176, -144,135,124,190,178,124,117,206,190,32,45,132,115,214,86,183,115,68,6,75,233,83,254,213,143,182,173,190,118,154,225,29,5,207, -231,174,116,91,14,222,39,192,171,206,173,145,28,15,252,205,99,32,23,61,39,74,185,85,167,141,13,17,241,133,220,234,252,54,236, -183,59,49,235,214,156,38,42,242,29,115,95,246,176,237,231,156,17,59,121,221,93,244,144,63,152,151,159,25,143,143,117,240,14,120, -152,124,222,86,47,188,234,46,65,238,135,43,97,77,55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,215,121,190,96, -94,157,175,200,55,74,225,31,230,249,194,175,241,89,49,141,21,226,239,215,216,155,142,100,115,185,235,197,165,245,71,197,245,130, -243,62,67,230,218,15,108,86,223,195,149,187,97,115,55,159,69,94,156,232,108,115,156,231,70,228,136,154,143,193,235,0,27,93,129, -117,104,117,33,154,30,65,236,9,93,71,183,226,121,149,171,150,78,150,59,10,57,159,171,210,197,81,118,84,230,2,39,215,127,2,245, -172,161,218,19,164,201,166,165,116,155,201,81,227,10,10,186,243,73,143,192,226,213,227,220,162,204,163,86,239,10,25,199,177,122, -162,92,90,75,200,92,85,82,23,91,44,142,213,109,181,225,91,67,42,102,182,154,30,29,69,85,244,228,168,233,165,240,19,121,174, -240,247,243,92,65,119,157,75,157,169,156,87,76,234,120,121,177,220,15,136,82,151,214,167,247,209,62,105,35,142,13,158,126,245, -61,127,57,102,90,33,109,227,37,175,205,231,231,185,242,253,207,64,54,54,71,4,122,23,22,5,138,3,115,90,144,248,4,172,200,117, -234,68,177,229,217,116,135,166,242,140,194,138,190,120,66,159,81,242,68,25,172,155,79,90,187,167,229,208,139,39,100,91,88,179, -86,122,174,58,97,108,89,86,39,12,34,100,113,248,241,86,225,209,55,25,117,139,137,92,199,107,243,97,212,6,93,200,197,237,160, -171,206,86,115,61,87,238,245,157,217,119,99,155,251,79,190,15,242,253,121,168,63,27,31,183,93,66,75,35,62,153,109,24,178,238, -63,250,213,61,148,223,197,5,140,201,193,75,168,219,207,245,57,114,191,27,20,235,87,191,169,8,20,113,236,227,93,195,107,196,223, -90,114,70,102,82,129,161,78,112,126,135,232,134,5,91,109,68,115,43,252,42,238,48,86,157,17,153,154,67,186,149,29,112,241,238, -25,116,69,166,138,193,155,43,179,219,192,156,106,215,2,248,203,14,218,231,78,172,195,9,24,243,33,171,193,138,59,218,201,86,34, -104,137,165,225,239,242,189,245,16,133,255,174,222,121,122,49,83,254,158,117,177,180,65,105,54,70,189,191,95,189,31,200,196, -164,58,196,36,117,15,85,86,250,176,182,71,196,63,79,238,126,126,55,165,252,194,162,155,250,213,111,63,120,142,94,185,51,56,186, -169,157,20,57,162,162,202,173,146,143,123,204,17,21,81,110,149,107,13,255,52,212,170,25,114,213,12,93,255,9,212,243,169,118, -187,244,230,0,234,98,236,41,50,91,177,179,187,140,178,187,136,61,31,178,176,46,124,175,251,164,91,135,43,17,186,24,187,66,101, -11,65,119,248,94,229,245,121,162,204,133,155,148,167,72,222,164,174,192,153,160,222,211,244,192,42,91,244,253,251,108,29,47, -206,209,249,174,65,67,151,214,239,24,202,190,219,249,75,191,243,221,206,185,98,62,157,103,148,211,185,102,133,124,107,165,110, -157,214,128,122,39,31,16,171,16,115,11,112,142,188,79,102,78,76,57,147,111,89,250,218,9,247,54,62,65,6,187,42,104,16,109,91, -150,190,120,98,123,87,57,109,55,203,81,126,254,196,96,215,124,240,113,106,46,125,230,68,160,48,252,116,230,29,31,206,168,1,245, -125,68,53,238,11,131,157,243,233,135,129,131,160,21,84,100,30,164,229,205,69,178,124,79,213,100,232,16,199,88,255,97,121,62, -109,239,196,153,141,157,18,248,211,231,171,10,68,145,184,148,194,47,65,235,223,213,13,25,121,211,128,246,123,216,172,18,8,98,46, -153,119,72,77,3,42,167,80,243,205,215,177,212,160,229,3,250,253,149,152,121,251,106,82,161,208,239,91,113,86,254,227,68,185, -81,135,251,192,46,17,164,85,2,89,180,168,196,154,181,32,35,143,131,19,148,252,240,243,153,28,191,80,234,16,250,187,18,33,51,24, -83,247,181,117,64,125,71,83,70,234,142,108,200,222,44,249,61,113,57,114,161,10,177,11,245,171,136,51,245,90,244,177,7,109,121, -38,65,201,15,191,152,185,147,231,233,62,42,178,125,148,203,223,192,10,34,202,216,130,231,122,83,64,125,199,116,27,232,61,217, -95,217,170,63,15,234,231,140,60,255,198,234,49,240,142,157,194,223,164,249,63,63,165,253,243,167,60,255,37,160,250,204,252,134, -137,223,57,90,69,234,183,69,197,69,234,187,152,242,34,245,155,9,31,232,152,214,27,7,173,193,243,133,160,75,138,212,111,63,150, -129,182,23,157,172,191,247,148,103,65,250,251,44,82,177,140,41,255,150,199,144,180,89,198,202,2,100,203,182,174,43,119,204,73, -144,202,105,12,253,100,106,186,134,102,126,167,149,249,30,201,144,180,73,62,47,212,252,238,172,156,79,183,85,191,50,81,125,173, -205,182,103,152,89,204,149,177,203,208,54,50,29,182,82,60,151,230,185,36,79,149,221,89,93,57,154,250,53,13,104,153,128,214,43, -223,179,211,204,247,103,114,143,201,12,90,217,158,101,249,251,228,133,186,95,254,46,119,161,222,139,181,248,107,105,234,215, -241,97,149,110,179,74,223,73,88,174,93,239,165,53,186,110,173,30,191,149,45,203,89,135,201,12,247,225,14,179,136,106,154,90,186, -90,155,214,47,239,172,95,223,179,190,181,126,89,87,75,75,125,231,202,229,205,245,43,186,215,183,44,91,223,189,172,123,101, -19,76,139,124,173,125,100,60,158,136,167,215,144,171,93,81,99,77,27,89,107,218,22,237,224,79,148,253,93,227,211,177,116,50,153, -30,27,136,38,162,123,98,83,180,250,84,78,40,54,53,149,156,90,29,26,73,78,143,143,134,18,201,116,104,79,44,29,202,74,133,250, -215,135,82,35,209,68,2,109,215,254,107,109,71,99,187,163,211,227,78,29,209,209,232,100,26,10,202,122,166,39,38,14,100,249,27, -163,233,116,119,116,124,124,87,116,228,66,18,125,100,244,245,147,217,215,223,79,149,125,91,67,235,247,143,196,38,211,241,36,130, -249,88,124,60,22,26,25,79,166,226,137,61,161,201,228,84,154,106,251,182,190,89,253,68,124,52,142,33,236,139,143,196,72,108,34, -107,211,246,238,245,84,184,105,122,36,54,128,154,190,196,228,116,122,27,171,8,100,88,91,167,211,25,158,47,195,147,79,197,153, -167,161,233,73,238,181,97,111,116,95,148,68,63,25,253,125,100,246,247,201,15,244,128,15,100,22,24,182,217,143,15,171,191,127, -103,63,213,244,71,19,163,83,201,248,104,227,174,204,108,27,179,243,238,84,230,104,163,5,111,37,213,35,231,208,70,85,111,37,196, -38,108,163,69,103,18,201,88,185,141,26,207,40,58,22,157,138,142,96,120,241,84,58,62,210,70,139,207,212,160,39,150,26,153,138, -79,166,147,83,179,15,100,60,54,35,223,31,27,82,190,52,251,220,33,202,245,51,163,125,19,125,44,180,33,62,142,65,214,116,77,199, -199,71,89,223,108,102,58,73,244,45,69,6,99,41,184,236,236,179,213,34,67,177,116,26,14,150,154,233,242,45,166,144,17,110,163, -121,89,161,145,100,34,29,75,164,27,187,153,238,71,103,149,217,170,137,216,104,60,218,200,174,219,200,14,151,89,250,37,111,45,208, -151,216,157,172,97,87,229,130,115,56,111,42,221,70,181,111,45,52,148,142,166,167,49,234,234,55,19,203,110,32,167,43,157,34,163, -163,67,141,82,57,179,154,43,207,212,96,107,66,53,217,58,25,75,196,70,251,225,129,49,233,43,161,51,52,124,139,185,207,236,110, -231,250,159,34,52,24,27,137,197,247,177,158,162,172,72,50,213,216,53,157,24,29,199,50,20,59,153,189,81,102,66,180,196,201,221, -22,157,26,137,141,111,159,142,143,182,81,32,91,49,157,142,143,55,246,39,247,156,198,219,22,141,79,57,250,202,242,218,104,251, -233,204,246,51,184,201,25,227,3,14,130,166,254,145,228,68,227,212,68,106,188,113,47,162,90,227,41,161,173,230,212,200,222,70, -205,103,104,113,90,68,109,163,165,255,98,19,231,154,44,249,23,219,40,233,254,51,72,207,88,37,235,131,111,122,226,180,81,207,191, -173,109,134,195,46,26,137,166,46,60,179,161,78,211,114,230,73,103,38,188,45,154,30,227,48,241,150,210,188,89,71,163,227,251, -226,23,54,34,180,38,177,129,113,40,54,174,79,232,3,177,123,60,154,194,134,14,206,34,211,199,145,88,215,87,205,82,63,16,155,216, -165,5,98,16,169,152,69,100,40,190,39,129,136,49,133,93,82,54,75,117,100,108,42,121,49,154,206,233,231,179,179,49,158,108,116, -28,220,109,84,168,216,227,209,196,158,70,61,142,34,7,171,15,113,82,218,43,224,96,110,221,181,55,54,146,62,153,55,148,158,194, -76,179,221,72,158,236,58,186,139,247,111,185,131,61,21,219,221,120,78,44,122,225,96,108,119,108,42,150,64,146,80,241,86,181, -188,249,101,181,220,141,157,83,83,209,3,28,150,50,61,157,204,109,163,238,217,216,237,255,206,106,175,225,67,111,86,37,167,77,119, -77,214,8,51,162,169,147,121,189,209,20,118,244,100,198,170,78,222,233,130,56,179,78,19,4,239,100,19,244,225,36,141,202,179,190, -192,193,149,54,241,159,194,104,163,150,83,56,237,103,60,128,215,156,172,87,118,95,232,96,68,226,19,236,16,115,78,101,169,173, -88,120,218,94,163,206,211,88,179,39,173,142,211,36,148,58,128,131,103,34,148,138,77,201,44,50,112,250,174,39,159,115,209,168, -214,121,228,55,116,119,246,247,119,117,118,111,190,32,114,238,182,245,23,12,116,70,186,123,47,232,223,58,20,33,177,131,140,29, -200,26,119,32,207,181,118,244,237,236,35,215,142,77,200,35,55,129,141,236,113,7,210,74,107,7,231,149,246,14,201,5,71,126,176, -116,191,170,68,217,230,207,77,138,32,23,221,177,147,4,210,79,40,51,144,119,26,195,93,84,61,124,230,84,168,126,248,223,74,45,106, -254,5,113,236,221,225,89,246,233,73,204,204,70,205,141,142,140,196,82,169,13,227,209,61,41,242,34,221,156,142,142,203,156,219, -157,185,42,152,209,209,81,126,26,157,130,28,249,116,239,125,137,209,216,126,180,86,79,178,133,55,58,57,169,51,42,114,69,83,202, -19,119,157,146,106,83,89,150,211,191,94,238,61,181,182,219,183,247,245,80,96,215,105,233,169,67,67,198,145,138,103,56,217,105, -167,28,114,23,232,59,71,206,174,116,167,30,181,103,87,90,201,65,76,151,82,124,160,195,4,228,218,149,230,195,136,236,93,156,77, -146,111,68,159,74,145,3,147,49,114,97,20,72,39,40,127,228,164,100,156,236,145,241,88,116,138,73,50,21,35,55,18,202,4,108,76, -185,186,32,21,122,56,205,140,198,19,41,201,150,165,205,177,3,82,88,218,200,167,11,145,228,118,232,176,177,11,18,105,18,163,228, -29,205,230,241,228,210,115,241,40,10,27,101,74,163,148,151,41,41,5,185,163,89,7,72,101,234,50,38,243,170,71,153,236,228,140,198, -167,48,68,132,125,176,227,169,204,208,93,177,139,176,244,41,202,145,155,178,59,57,10,3,198,50,7,4,53,236,142,226,106,55,26, -74,39,67,35,83,177,104,58,22,218,53,61,174,239,148,74,119,104,247,84,114,34,148,113,19,207,238,120,34,58,30,127,71,140,170,80, -26,157,89,168,13,201,41,199,237,75,9,87,178,72,102,67,207,38,96,239,142,79,193,153,124,187,97,162,209,204,130,123,185,67,229, -198,100,237,97,131,231,240,167,50,134,137,72,66,94,124,100,84,228,114,121,92,186,118,138,202,248,65,121,238,105,215,242,249, -51,117,167,199,176,57,92,57,57,57,30,31,145,167,106,198,219,139,192,62,109,208,165,78,166,51,167,151,90,78,191,136,145,7,108,121, -246,82,33,74,61,234,238,158,217,54,57,146,37,125,33,63,91,84,107,237,205,62,167,200,141,178,116,190,69,40,244,78,79,112,56,199, -70,198,225,171,44,53,171,117,33,10,199,146,100,84,106,96,189,84,141,2,31,144,167,25,99,75,116,130,153,125,61,41,170,59,93,70, -102,161,167,9,134,79,23,84,185,231,105,146,243,32,201,213,167,14,19,147,155,171,171,122,178,206,140,233,232,33,179,6,185,200, -188,194,242,33,47,243,48,205,185,19,249,245,35,31,19,220,172,71,218,91,249,131,20,157,74,78,198,166,210,113,244,83,128,199,193, -216,68,50,29,203,4,13,48,134,228,81,164,163,149,236,82,6,136,188,49,121,11,209,247,22,114,143,69,83,91,216,37,60,40,140,201, -93,100,141,37,225,187,57,252,169,124,83,196,201,140,143,238,39,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242, -252,208,173,118,104,12,19,141,167,214,79,76,166,15,112,65,218,153,171,103,94,164,120,226,58,37,32,15,167,55,189,220,175,111,175, -243,69,138,121,33,2,144,11,31,156,97,120,199,147,136,117,42,144,187,39,180,135,91,124,158,144,119,34,107,102,42,156,56,109, -27,228,79,156,180,10,148,59,225,8,196,198,196,4,153,19,169,61,248,72,79,147,149,224,181,176,249,19,81,33,17,187,152,247,0,140, -146,96,35,153,201,93,123,201,149,220,189,59,133,225,4,146,137,174,104,122,100,108,38,7,73,81,9,246,216,73,129,23,79,137,61,176, -68,241,169,21,236,230,52,231,84,238,57,83,48,137,212,162,108,136,61,43,251,87,106,200,159,76,204,188,51,145,26,10,157,28,213, -58,47,169,239,194,112,68,244,156,159,60,233,106,204,125,58,159,123,98,227,209,3,96,23,100,216,236,72,251,156,114,42,8,100,38, -226,78,38,54,140,79,167,198,200,151,76,12,164,167,51,108,140,140,199,163,188,112,48,149,138,83,41,115,198,227,188,149,229,184, -186,147,19,147,136,192,144,69,75,153,80,200,8,157,121,82,22,132,113,145,13,37,164,189,180,235,166,122,56,230,227,138,13,217, -34,184,124,226,148,24,69,94,102,234,114,30,151,103,28,172,132,31,79,186,106,158,19,79,143,97,43,149,102,42,102,46,148,186,38, -144,169,113,240,242,153,231,120,217,151,195,207,106,39,122,146,153,188,46,39,83,66,128,194,224,248,16,75,206,52,177,147,23,115, -200,44,154,132,251,157,58,129,178,89,152,67,233,216,100,228,226,36,149,156,84,55,19,76,200,154,228,244,209,146,239,52,115,38, -101,186,197,251,194,51,169,51,47,85,146,129,37,63,83,210,17,75,214,200,236,51,47,83,82,27,93,86,200,40,145,159,41,69,146,27,112, -214,145,61,41,103,107,242,22,158,59,21,219,195,239,87,166,78,126,73,67,174,41,233,57,228,85,84,133,6,85,86,249,214,188,41,28, -217,177,84,122,198,183,183,77,197,147,240,141,3,220,86,46,191,123,74,111,36,48,210,251,162,227,100,77,177,47,153,83,211,9,42, -76,101,179,80,253,30,141,138,82,142,236,57,195,116,103,94,58,123,82,35,99,177,81,28,251,228,74,197,144,54,140,146,149,98,223, -42,227,79,245,182,119,44,58,26,234,219,26,154,201,27,60,92,199,102,166,2,236,241,110,103,106,149,11,6,123,234,0,7,201,124,126, -208,153,224,116,124,20,149,99,124,41,192,94,193,68,173,20,39,18,118,74,62,228,72,194,13,41,79,21,211,201,73,249,232,74,169,227, -213,74,129,131,158,51,252,28,120,79,102,149,211,99,113,24,131,63,107,154,80,129,11,11,26,77,76,146,59,157,148,183,54,242,164, -147,58,167,152,51,157,152,205,187,230,157,194,118,248,80,233,116,226,77,214,210,134,237,167,113,58,72,178,117,55,69,196,205, -194,157,111,188,76,109,251,141,171,47,109,171,167,152,120,15,24,116,129,36,135,76,58,34,44,218,73,224,124,85,8,178,172,7,140,213, -163,238,252,19,38,221,111,228,237,180,137,62,33,196,231,89,254,22,97,124,68,220,111,184,243,47,236,55,233,54,97,213,95,111,83, -199,254,126,23,53,28,121,7,196,62,32,164,190,195,82,95,195,254,16,237,21,223,54,220,75,32,251,1,97,54,24,21,23,27,123,42,250, -77,241,65,145,211,240,158,134,157,166,241,160,145,251,225,157,166,249,85,35,127,243,206,142,71,250,182,218,134,109,210,141,74, -201,17,250,190,176,94,23,135,196,103,140,103,240,216,94,143,63,237,244,186,32,119,197,150,183,111,62,80,95,111,76,87,84,154, -244,5,209,64,199,193,204,111,111,167,163,6,207,224,105,126,162,107,101,249,253,134,245,119,113,153,113,139,248,33,198,92,127, -11,221,96,152,234,25,117,207,177,220,163,59,59,232,165,76,225,78,195,84,29,170,238,232,167,198,44,157,221,107,168,206,94,146, -29,124,69,126,126,111,70,237,150,11,140,75,50,162,95,151,149,223,146,159,55,153,6,6,143,65,180,215,211,189,166,241,21,113,35, -143,225,30,211,228,210,227,232,145,62,239,40,127,154,203,143,27,255,132,76,199,230,15,211,23,249,241,211,170,234,1,71,249,33, -46,255,67,149,31,228,242,151,13,89,254,50,119,32,75,119,103,75,223,54,45,250,140,184,77,60,8,157,59,121,118,199,76,140,171,163, -29,139,243,37,99,109,255,206,225,53,91,206,95,83,111,147,177,191,205,69,244,146,172,236,143,155,226,57,81,116,224,17,185,160, -245,231,219,100,139,249,149,171,232,160,197,83,122,183,252,124,15,127,118,28,218,31,44,167,235,45,118,179,10,227,176,213,102, -188,126,201,146,250,71,251,141,252,139,141,125,21,251,247,239,63,16,71,55,162,91,233,91,189,198,22,244,126,151,92,102,17,240, -91,198,171,162,178,243,144,179,171,111,115,79,182,65,71,181,208,92,191,73,183,139,38,200,28,53,106,111,227,74,186,220,195,253, -30,54,141,255,21,221,65,147,158,21,66,184,109,50,5,10,143,88,38,107,20,134,45,92,36,114,109,114,137,74,219,172,151,26,127,105, -137,187,97,142,142,120,229,176,101,220,105,44,31,22,197,126,83,220,110,212,237,55,14,124,131,37,214,187,12,140,245,255,196,122, -186,218,35,238,224,5,16,129,2,139,88,225,175,67,54,205,175,60,139,254,100,153,183,139,95,139,63,112,101,187,153,243,89,67,244, -155,38,84,52,30,50,106,150,24,219,43,108,211,206,89,238,50,93,57,123,45,247,221,104,215,176,217,116,221,38,10,27,224,22,47,137, -5,13,123,77,227,19,198,188,122,12,143,118,217,6,246,206,173,205,152,145,105,187,108,183,113,17,140,143,150,46,151,123,175,233, -249,173,152,35,165,132,233,34,195,87,1,33,136,216,158,74,186,7,54,175,24,238,24,22,115,11,48,118,209,244,77,91,172,110,175,228, -39,227,101,81,7,147,218,70,3,27,22,91,180,178,179,127,255,59,174,180,105,184,157,94,112,203,169,99,222,247,24,203,119,30,49, -177,3,138,238,229,185,7,226,183,236,91,213,191,200,22,219,176,135,239,182,217,228,21,29,123,227,43,140,253,21,135,154,91,227, -13,149,116,92,46,245,143,228,231,119,221,226,86,168,57,96,186,49,241,142,251,228,178,5,77,227,21,33,110,189,213,180,160,13,179, -189,219,16,152,180,121,155,33,246,110,54,237,59,140,165,113,225,181,237,6,151,93,203,38,198,92,45,219,182,93,198,72,133,237,94, -238,18,46,195,101,241,148,141,75,218,80,225,50,74,54,177,20,252,236,117,183,113,183,113,59,59,64,113,129,73,159,54,234,46,196, -0,239,113,161,71,119,254,37,229,244,172,75,60,193,107,185,217,244,96,12,232,246,118,33,130,166,251,43,194,174,52,115,158,23, -107,159,57,16,124,196,180,57,20,109,54,173,235,196,210,91,133,199,118,215,163,159,242,139,121,0,247,154,185,240,166,251,68,161, -223,206,13,26,163,21,24,132,125,139,229,125,141,87,108,111,67,251,218,117,182,119,133,26,40,91,221,206,89,197,99,116,121,92,57, -174,92,35,221,102,231,178,60,125,204,173,198,0,71,68,239,143,30,62,193,219,208,62,98,4,111,182,205,160,177,187,2,61,99,108, -143,152,66,13,131,48,140,67,150,45,251,232,119,153,13,237,93,114,233,237,205,77,210,48,22,61,160,102,215,113,216,34,237,58,240, -229,194,1,83,200,89,89,247,25,107,59,130,188,29,120,161,15,139,194,130,96,53,214,186,125,205,48,154,195,170,193,114,24,232, -105,151,218,241,159,178,13,118,83,148,110,179,5,91,145,224,224,71,132,123,137,26,134,49,103,204,72,84,116,196,141,188,37,198, -190,182,91,132,223,47,59,105,248,72,195,60,250,133,20,204,143,211,29,46,185,216,59,233,25,233,22,249,70,120,204,24,174,232,56, -124,136,227,138,141,142,207,179,13,172,48,236,200,147,131,5,218,113,34,108,182,172,215,102,198,126,73,167,109,194,139,97,72, -172,47,124,27,86,171,52,5,199,199,191,27,230,157,226,179,226,189,58,188,211,251,77,113,208,112,87,180,204,251,98,189,73,55,25, -117,116,136,163,16,182,159,73,191,17,77,159,183,169,29,145,233,81,236,251,138,122,106,103,87,252,130,71,124,10,190,138,16,118, -88,152,134,111,137,113,113,197,133,29,155,247,195,115,110,145,53,249,135,106,141,119,84,40,142,17,22,63,54,74,235,140,69,198, -127,11,43,231,31,162,36,215,88,12,78,185,85,114,126,137,93,50,84,226,81,143,118,137,40,137,130,177,164,36,71,138,214,228,184, -32,91,58,211,108,174,177,132,229,68,233,210,25,94,137,82,94,171,56,6,56,249,51,197,248,140,220,30,227,44,110,107,148,214,148, -46,200,116,119,158,236,189,102,166,127,102,156,93,178,33,195,112,151,156,3,198,26,160,9,82,222,12,147,165,122,75,182,227,179, -39,195,244,96,232,67,37,102,86,54,171,209,144,42,122,51,12,23,24,51,50,114,112,94,12,110,175,26,156,171,180,182,116,97,105,85, -105,168,180,178,180,90,148,88,194,20,57,102,153,129,63,194,104,57,120,208,58,182,112,153,56,88,39,196,109,192,147,192,225,48, -220,30,56,14,60,185,72,136,155,206,18,130,255,145,50,185,182,93,230,33,49,11,200,54,114,92,250,135,56,130,58,46,59,104,189,123, -137,117,185,65,107,196,189,75,132,117,125,189,16,15,212,27,226,231,160,47,3,239,110,16,226,30,224,81,224,96,35,2,188,59,87,182, -235,69,187,7,27,251,196,175,26,133,245,104,147,16,207,2,135,155,133,184,17,248,21,240,151,102,50,132,55,223,16,87,135,118,64, -244,240,210,115,196,109,75,133,120,16,56,6,60,11,92,223,34,196,93,192,195,192,147,192,243,192,27,45,100,9,219,143,201,10,110, -26,69,211,171,151,237,18,15,44,195,8,150,11,241,216,10,104,7,14,175,36,183,59,80,172,196,244,223,61,144,125,114,165,97,92,181, -74,24,215,174,54,141,55,86,11,227,141,54,211,184,171,195,107,220,180,102,204,122,99,173,41,30,239,130,165,186,77,241,100,15, -102,215,99,136,171,214,99,164,27,48,132,141,120,6,158,221,4,221,3,232,99,11,248,192,213,91,13,113,207,86,240,183,193,18,103,195, -186,103,195,2,198,124,193,127,14,10,116,120,253,208,101,152,212,16,38,19,225,159,231,5,189,194,251,46,113,248,160,245,66,132, -107,15,111,23,57,55,1,87,237,200,252,255,74,206,223,244,100,254,239,64,254,173,74,230,255,15,228,223,169,100,254,15,65,254,157, -74,136,212,255,35,200,191,213,201,252,95,130,46,154,249,255,4,77,191,250,29,141,252,61,85,72,253,63,82,219,192,112,133,148,12, -255,123,122,225,87,191,125,231,127,3,111,132,84,191,252,255,15,154,90,158,255,141,182,21,82,191,75,226,127,199,109,135,212,248, -248,223,224,147,214,195,255,38,159,127,204,195,124,254,127,15,255,63,171,27,97,244,48,81,0,0,0,0}; - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \ - STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;") - -DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode)) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \ - METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \ - METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \ - METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 23) -#undef JNI_CLASS_MEMBERS - -#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") \ - METHOD (getName, "getName", "()Ljava/lang/String;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 23) -#undef JNI_CLASS_MEMBERS - -//============================================================================== -class MidiInput::Pimpl -{ -public: - Pimpl (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) - : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), - javaMidiDevice (LocalRef(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, - (jint) deviceID, (jlong) this))) - { - } - - ~Pimpl() - { - if (jobject d = javaMidiDevice.get()) - { - getEnv()->CallVoidMethod (d, JuceMidiPort.close); - javaMidiDevice.clear(); - } - } - - bool isOpen() const noexcept - { - return javaMidiDevice != nullptr; - } - - void start() - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, JuceMidiPort.start); - } - - void stop() - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, JuceMidiPort.stop); - - callback = nullptr; - } - - String getName() const noexcept - { - if (jobject d = javaMidiDevice.get()) - return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); - - return {}; - } - - void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) - { - auto* env = getEnv(); - - jassert (byteArray != nullptr); - auto* data = env->GetByteArrayElements (byteArray, nullptr); - - HeapBlock buffer (static_cast (len)); - std::memcpy (buffer.get(), data + offset, static_cast (len)); - - midiConcatenator.pushMidiData (buffer.get(), - len, static_cast (timestamp) * 1.0e-9, - juceMidiInput, *callback); - - 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: - MidiInput* juceMidiInput; - MidiInputCallback* callback; - MidiDataConcatenator midiConcatenator; - GlobalRef javaMidiDevice; -}; - -//============================================================================== -class MidiOutput::Pimpl -{ -public: - Pimpl (const LocalRef& midiDevice) - : javaMidiDevice (midiDevice) - { - } - - ~Pimpl() - { - if (jobject d = javaMidiDevice.get()) - { - getEnv()->CallVoidMethod (d, JuceMidiPort.close); - javaMidiDevice.clear(); - } - } - - void send (jbyteArray byteArray, jint offset, jint len) - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, - JuceMidiPort.sendMidi, - byteArray, offset, len); - } - - String getName() const noexcept - { - if (jobject d = javaMidiDevice.get()) - return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); - - return {}; - } - -private: - GlobalRef javaMidiDevice; -}; - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (MidiInput::Pimpl::handleReceive, "handleReceive", "(J[BIIJ)V" ) - -DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) -#undef JNI_CLASS_MEMBERS - -//============================================================================== -class AndroidMidiDeviceManager -{ -public: - AndroidMidiDeviceManager() - : deviceManager (LocalRef(getEnv()->CallStaticObjectMethod (JuceMidiSupport, - JuceMidiSupport.getAndroidMidiDeviceManager, - getAppContext().get()))) - { - } - - Array getDevices (bool input) - { - if (jobject dm = deviceManager.get()) - { - jobjectArray jDeviceNameAndIDs - = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs - : MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs); - - // Create a local reference as converting this to a JUCE string will call into JNI - LocalRef localDeviceNameAndIDs (jDeviceNameAndIDs); - - auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs); - deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - - Array devices; - - for (int i = 0; i < deviceNameAndIDs.size(); i += 2) - devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] }); - - return devices; - } - - return {}; - } - - MidiInput::Pimpl* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) - { - if (auto dm = deviceManager.get()) - { - auto androidMidiInput = std::make_unique (juceMidiInput, deviceID, callback, dm); - - if (androidMidiInput->isOpen()) - return androidMidiInput.release(); - } - - return nullptr; - } - - MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) - { - if (auto dm = deviceManager.get()) - if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) - return new MidiOutput::Pimpl (LocalRef(javaMidiPort)); - - return nullptr; - } - -private: - GlobalRef deviceManager; -}; - -//============================================================================== -Array MidiInput::getAvailableDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - AndroidMidiDeviceManager manager; - return manager.getDevices (true); -} - -MidiDeviceInfo MidiInput::getDefaultDevice() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) -{ - if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) - return {}; - - AndroidMidiDeviceManager manager; - - std::unique_ptr midiInput (new MidiInput ({}, deviceIdentifier)); - - if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) - { - midiInput->internal.reset (port); - midiInput->setName (port->getName()); - - return midiInput; - } - - return {}; -} - -StringArray MidiInput::getDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - StringArray deviceNames; - - for (auto& d : getAvailableDevices()) - deviceNames.add (d.name); - - return deviceNames; -} - -int MidiInput::getDefaultDeviceIndex() -{ - return (getAndroidSDKVersion() < 23 ? -1 : 0); -} - -std::unique_ptr MidiInput::openDevice (int index, MidiInputCallback* callback) -{ - return openDevice (getAvailableDevices()[index].identifier, callback); -} - -MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) - : deviceInfo (deviceName, deviceIdentifier) -{ -} - -MidiInput::~MidiInput() = default; - -void MidiInput::start() -{ - if (auto* mi = internal.get()) - mi->start(); -} - -void MidiInput::stop() -{ - if (auto* mi = internal.get()) - mi->stop(); -} - -//============================================================================== -Array MidiOutput::getAvailableDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - AndroidMidiDeviceManager manager; - return manager.getDevices (false); -} - -MidiDeviceInfo MidiOutput::getDefaultDevice() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - return getAvailableDevices().getFirst(); -} - -std::unique_ptr MidiOutput::openDevice (const String& deviceIdentifier) -{ - if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) - return {}; - - AndroidMidiDeviceManager manager; - - if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) - { - std::unique_ptr midiOutput (new MidiOutput ({}, deviceIdentifier)); - midiOutput->internal.reset (port); - midiOutput->setName (port->getName()); - - return midiOutput; - } - - return {}; -} - -StringArray MidiOutput::getDevices() -{ - if (getAndroidSDKVersion() < 23) - return {}; - - StringArray deviceNames; - - for (auto& d : getAvailableDevices()) - deviceNames.add (d.name); - - return deviceNames; -} - -int MidiOutput::getDefaultDeviceIndex() -{ - return (getAndroidSDKVersion() < 23 ? -1 : 0); -} - -std::unique_ptr MidiOutput::openDevice (int index) -{ - return openDevice (getAvailableDevices()[index].identifier); -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - if (auto* androidMidi = internal.get()) - { - auto* env = getEnv(); - auto messageSize = message.getRawDataSize(); - - LocalRef messageContent (env->NewByteArray (messageSize)); - auto content = messageContent.get(); - - auto* rawBytes = env->GetByteArrayElements (content, nullptr); - std::memcpy (rawBytes, message.getRawData(), static_cast (messageSize)); - env->ReleaseByteArrayElements (content, rawBytes, 0); - - androidMidi->send (content, (jint) 0, (jint) messageSize); - } -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_android_Oboe.cpp b/source/modules/juce_audio_devices/native/juce_android_Oboe.cpp deleted file mode 100644 index 105a182a2..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_Oboe.cpp +++ /dev/null @@ -1,1439 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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. - - ============================================================================== -*/ - -#ifndef JUCE_OBOE_LOG_ENABLED - #define JUCE_OBOE_LOG_ENABLED 1 -#endif - -#if JUCE_OBOE_LOG_ENABLED - #define JUCE_OBOE_LOG(x) DBG(x) -#else - #define JUCE_OBOE_LOG(x) {} -#endif - -namespace juce -{ - -template struct OboeAudioIODeviceBufferHelpers {}; - -template <> -struct OboeAudioIODeviceBufferHelpers -{ - static oboe::AudioFormat oboeAudioFormat() { return oboe::AudioFormat::I16; } - - static constexpr int bitDepth() { return 16; } - - static bool referAudioBufferDirectlyToOboeIfPossible (int16*, AudioBuffer&, int) { return false; } - - using NativeInt16 = AudioData::Format; - using NativeFloat32 = AudioData::Format; - - static void convertFromOboe (const int16* srcInterleaved, AudioBuffer& audioBuffer, int numSamples) - { - const auto numChannels = audioBuffer.getNumChannels(); - - AudioData::deinterleaveSamples (AudioData::InterleavedSource { reinterpret_cast (srcInterleaved), numChannels }, - AudioData::NonInterleavedDest { audioBuffer.getArrayOfWritePointers(), numChannels }, - numSamples); - } - - static void convertToOboe (const AudioBuffer& audioBuffer, int16* dstInterleaved, int numSamples) - { - const auto numChannels = audioBuffer.getNumChannels(); - - AudioData::interleaveSamples (AudioData::NonInterleavedSource { audioBuffer.getArrayOfReadPointers(), numChannels }, - AudioData::InterleavedDest { reinterpret_cast (dstInterleaved), numChannels }, - numSamples); - } -}; - -template <> -struct OboeAudioIODeviceBufferHelpers -{ - static oboe::AudioFormat oboeAudioFormat() { return oboe::AudioFormat::Float; } - - static constexpr int bitDepth() { return 32; } - - static bool referAudioBufferDirectlyToOboeIfPossible (float* nativeBuffer, AudioBuffer& audioBuffer, int numSamples) - { - if (audioBuffer.getNumChannels() == 1) - { - audioBuffer.setDataToReferTo (&nativeBuffer, 1, numSamples); - return true; - } - - return false; - } - - using Format = AudioData::Format; - - static void convertFromOboe (const float* srcInterleaved, AudioBuffer& audioBuffer, int numSamples) - { - auto numChannels = audioBuffer.getNumChannels(); - - if (numChannels > 0) - { - // No need to convert, we instructed the buffer to point to the src data directly already - jassert (audioBuffer.getWritePointer (0) != srcInterleaved); - - AudioData::deinterleaveSamples (AudioData::InterleavedSource { srcInterleaved, numChannels }, - AudioData::NonInterleavedDest { audioBuffer.getArrayOfWritePointers(), numChannels }, - numSamples); - } - } - - static void convertToOboe (const AudioBuffer& audioBuffer, float* dstInterleaved, int numSamples) - { - auto numChannels = audioBuffer.getNumChannels(); - - if (numChannels > 0) - { - // No need to convert, we instructed the buffer to point to the src data directly already - jassert (audioBuffer.getReadPointer (0) != dstInterleaved); - - AudioData::interleaveSamples (AudioData::NonInterleavedSource { audioBuffer.getArrayOfReadPointers(), numChannels }, - AudioData::InterleavedDest { dstInterleaved, numChannels }, - numSamples); - } - } -}; - -template -static String getOboeString (const Type& value) -{ - return String (oboe::convertToText (value)); -} - -//============================================================================== -class OboeAudioIODevice : public AudioIODevice -{ -public: - //============================================================================== - OboeAudioIODevice (const String& deviceName, - int inputDeviceIdToUse, - const Array& supportedInputSampleRatesToUse, - int maxNumInputChannelsToUse, - int outputDeviceIdToUse, - const Array& supportedOutputSampleRatesToUse, - int maxNumOutputChannelsToUse) - : AudioIODevice (deviceName, oboeTypeName), - inputDeviceId (inputDeviceIdToUse), - supportedInputSampleRates (supportedInputSampleRatesToUse), - maxNumInputChannels (maxNumInputChannelsToUse), - outputDeviceId (outputDeviceIdToUse), - supportedOutputSampleRates (supportedOutputSampleRatesToUse), - maxNumOutputChannels (maxNumOutputChannelsToUse) - { - } - - ~OboeAudioIODevice() override - { - close(); - } - - StringArray getOutputChannelNames() override { return getChannelNames (false); } - StringArray getInputChannelNames() override { return getChannelNames (true); } - - Array getAvailableSampleRates() override - { - Array result; - - auto inputSampleRates = getAvailableSampleRates (true); - auto outputSampleRates = getAvailableSampleRates (false); - - if (inputDeviceId == -1) - { - for (auto& sr : outputSampleRates) - result.add (sr); - } - else if (outputDeviceId == -1) - { - for (auto& sr : inputSampleRates) - result.add (sr); - } - else - { - // For best performance, the same sample rate should be used for input and output, - for (auto& inputSampleRate : inputSampleRates) - { - if (outputSampleRates.contains (inputSampleRate)) - result.add (inputSampleRate); - } - } - - // either invalid device was requested or its input&output don't have compatible sample rate - jassert (result.size() > 0); - return result; - } - - Array getAvailableBufferSizes() override - { - return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (getNativeBufferSize(), getAvailableSampleRates()); - } - - String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double requestedSampleRate, int bufferSize) override - { - close(); - - lastError.clear(); - - sampleRate = (int) (requestedSampleRate > 0 ? requestedSampleRate : AndroidHighPerformanceAudioHelpers::getNativeSampleRate()); - actualBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; - - // The device may report no max, claiming "no limits". Pick sensible defaults. - int maxOutChans = maxNumOutputChannels > 0 ? maxNumOutputChannels : 2; - int maxInChans = maxNumInputChannels > 0 ? maxNumInputChannels : 1; - - activeOutputChans = outputChannels; - activeOutputChans.setRange (maxOutChans, - activeOutputChans.getHighestBit() + 1 - maxOutChans, - false); - - activeInputChans = inputChannels; - activeInputChans.setRange (maxInChans, - activeInputChans.getHighestBit() + 1 - maxInChans, - false); - - int numOutputChans = activeOutputChans.countNumberOfSetBits(); - int numInputChans = activeInputChans.countNumberOfSetBits(); - - if (numInputChans > 0 && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))) - { - // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio - // before trying to open an audio input device. This is not going to work! - jassertfalse; - lastError = "Error opening Oboe input device: the app was not granted android.permission.RECORD_AUDIO"; - } - - // At least one output channel should be set! - jassert (numOutputChans >= 0); - - session.reset (OboeSessionBase::create (*this, - inputDeviceId, outputDeviceId, - numInputChans, numOutputChans, - sampleRate, actualBufferSize)); - - deviceOpen = session != nullptr; - - if (! deviceOpen) - lastError = "Failed to create audio session"; - - return lastError; - } - - void close() override { stop(); } - int getOutputLatencyInSamples() override { return session->getOutputLatencyInSamples(); } - int getInputLatencyInSamples() override { return session->getInputLatencyInSamples(); } - bool isOpen() override { return deviceOpen; } - int getCurrentBufferSizeSamples() override { return actualBufferSize; } - int getCurrentBitDepth() override { return session->getCurrentBitDepth(); } - BigInteger getActiveOutputChannels() const override { return activeOutputChans; } - BigInteger getActiveInputChannels() const override { return activeInputChans; } - String getLastError() override { return lastError; } - bool isPlaying() override { return callback.get() != nullptr; } - int getXRunCount() const noexcept override { return session->getXRunCount(); } - - int getDefaultBufferSize() override - { - return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (getNativeBufferSize(), getCurrentSampleRate()); - } - - double getCurrentSampleRate() override - { - return (sampleRate == 0.0 ? AndroidHighPerformanceAudioHelpers::getNativeSampleRate() : sampleRate); - } - - void start (AudioIODeviceCallback* newCallback) override - { - if (callback.get() != newCallback) - { - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (this); - - AudioIODeviceCallback* oldCallback = callback.get(); - - if (oldCallback != nullptr) - { - // already running - if (newCallback == nullptr) - stop(); - else - setCallback (newCallback); - - oldCallback->audioDeviceStopped(); - } - else - { - jassert (newCallback != nullptr); - - // session hasn't started yet - setCallback (newCallback); - running = true; - - session->start(); - } - - callback = newCallback; - } - } - - void stop() override - { - if (session != nullptr) - session->stop(); - - running = false; - setCallback (nullptr); - } - - bool setAudioPreprocessingEnabled (bool) override - { - // Oboe does not expose this setting, yet it may use preprocessing - // for older APIs running OpenSL - return false; - } - - static const char* const oboeTypeName; - -private: - StringArray getChannelNames (bool forInput) - { - auto& deviceId = forInput ? inputDeviceId : outputDeviceId; - auto& numChannels = forInput ? maxNumInputChannels : maxNumOutputChannels; - - // If the device id is unknown (on olders APIs) or if the device claims to - // support "any" channel count, use a sensible default - if (deviceId == -1 || numChannels == -1) - return forInput ? StringArray ("Input") : StringArray ("Left", "Right"); - - StringArray names; - - for (int i = 0; i < numChannels; ++i) - names.add ("Channel " + String (i + 1)); - - return names; - } - - Array getAvailableSampleRates (bool forInput) - { - auto& supportedSampleRates = forInput - ? supportedInputSampleRates - : supportedOutputSampleRates; - - if (! supportedSampleRates.isEmpty()) - return supportedSampleRates; - - // device claims that it supports "any" sample rate, use - // standard ones then - return getDefaultSampleRates(); - } - - static Array getDefaultSampleRates() - { - static const int standardRates[] = { 8000, 11025, 12000, 16000, - 22050, 24000, 32000, 44100, 48000 }; - - Array rates (standardRates, numElementsInArray (standardRates)); - - // make sure the native sample rate is part of the list - int native = (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(); - - if (native != 0 && ! rates.contains (native)) - rates.add (native); - - return rates; - } - - static int getNativeBufferSize() - { - auto bufferSizeHint = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(); - - // providing a callback is required on some devices to get a FAST track, so we pass an - // empty one to the temp stream to get the best available buffer size - struct DummyCallback : public oboe::AudioStreamCallback - { - oboe::DataCallbackResult onAudioReady (oboe::AudioStream*, void*, int32_t) override { return oboe::DataCallbackResult::Stop; } - }; - - DummyCallback callback; - - // NB: Exclusive mode could be rejected if a device is already opened in that mode, so to get - // reliable results, only use this function when a device is closed. - // We initially try to open a stream with a buffer size returned from - // android.media.property.OUTPUT_FRAMES_PER_BUFFER property, but then we verify the actual - // size after the stream is open. - OboeAudioIODevice::OboeStream tempStream (oboe::kUnspecified, - oboe::Direction::Output, - oboe::SharingMode::Exclusive, - 2, - getAndroidSDKVersion() >= 21 ? oboe::AudioFormat::Float : oboe::AudioFormat::I16, - (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), - bufferSizeHint, - &callback); - - if (auto* nativeStream = tempStream.getNativeStream()) - return nativeStream->getFramesPerBurst(); - - return bufferSizeHint; - } - - void setCallback (AudioIODeviceCallback* callbackToUse) - { - if (! running) - { - callback.set (callbackToUse); - return; - } - - // Setting nullptr callback is allowed only when playback is stopped. - jassert (callbackToUse != nullptr); - - for (;;) - { - auto old = callback.get(); - - if (old == callbackToUse) - break; - - // If old is nullptr, then it means that it's currently being used! - if (old != nullptr && callback.compareAndSetBool (callbackToUse, old)) - break; - - Thread::sleep (1); - } - } - - void process (const float** inputChannelData, int numInputChannels, - float** outputChannelData, int numOutputChannels, int32_t numFrames) - { - if (auto* cb = callback.exchange (nullptr)) - { - cb->audioDeviceIOCallbackWithContext (inputChannelData, - numInputChannels, - outputChannelData, - numOutputChannels, - numFrames, - {}); - callback.set (cb); - } - else - { - for (int i = 0; i < numOutputChannels; ++i) - zeromem (outputChannelData[i], (size_t) (numFrames) * sizeof (float)); - } - } - - //============================================================================== - class OboeStream - { - public: - OboeStream (int deviceId, oboe::Direction direction, - oboe::SharingMode sharingMode, - int channelCount, oboe::AudioFormat format, - int32 sampleRateIn, int32 bufferSize, - oboe::AudioStreamCallback* callbackIn = nullptr) - { - open (deviceId, direction, sharingMode, channelCount, - format, sampleRateIn, bufferSize, callbackIn); - } - - ~OboeStream() - { - close(); - delete stream; - } - - bool openedOk() const noexcept - { - return openResult == oboe::Result::OK; - } - - void start() - { - jassert (openedOk()); - - if (openedOk() && stream != nullptr) - { - auto expectedState = oboe::StreamState::Starting; - auto nextState = oboe::StreamState::Started; - int64 timeoutNanos = 1000 * oboe::kNanosPerMillisecond; - - auto startResult = stream->requestStart(); - JUCE_OBOE_LOG ("Requested Oboe stream start with result: " + getOboeString (startResult)); - - startResult = stream->waitForStateChange (expectedState, &nextState, timeoutNanos); - - JUCE_OBOE_LOG ("Starting Oboe stream with result: " + getOboeString (startResult); - + "\nUses AAudio = " + String ((int) stream->usesAAudio()) - + "\nDirection = " + getOboeString (stream->getDirection()) - + "\nSharingMode = " + getOboeString (stream->getSharingMode()) - + "\nChannelCount = " + String (stream->getChannelCount()) - + "\nFormat = " + getOboeString (stream->getFormat()) - + "\nSampleRate = " + String (stream->getSampleRate()) - + "\nBufferSizeInFrames = " + String (stream->getBufferSizeInFrames()) - + "\nBufferCapacityInFrames = " + String (stream->getBufferCapacityInFrames()) - + "\nFramesPerBurst = " + String (stream->getFramesPerBurst()) - + "\nFramesPerCallback = " + String (stream->getFramesPerCallback()) - + "\nBytesPerFrame = " + String (stream->getBytesPerFrame()) - + "\nBytesPerSample = " + String (stream->getBytesPerSample()) - + "\nPerformanceMode = " + getOboeString (stream->getPerformanceMode()) - + "\ngetDeviceId = " + String (stream->getDeviceId())); - } - } - - oboe::AudioStream* getNativeStream() const - { - jassert (openedOk()); - return stream; - } - - int getXRunCount() const - { - if (stream != nullptr) - { - auto count = stream->getXRunCount(); - - if (count) - return count.value(); - - JUCE_OBOE_LOG ("Failed to get Xrun count: " + getOboeString (count.error())); - } - - return 0; - } - - private: - void open (int deviceId, oboe::Direction direction, - oboe::SharingMode sharingMode, - int channelCount, oboe::AudioFormat format, - int32 newSampleRate, int32 newBufferSize, - oboe::AudioStreamCallback* newCallback = nullptr) - { - oboe::DefaultStreamValues::FramesPerBurst = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(); - - oboe::AudioStreamBuilder builder; - - if (deviceId != -1) - builder.setDeviceId (deviceId); - - // Note: letting OS to choose the buffer capacity & frames per callback. - builder.setDirection (direction); - builder.setSharingMode (sharingMode); - builder.setChannelCount (channelCount); - builder.setFormat (format); - builder.setSampleRate (newSampleRate); - builder.setPerformanceMode (oboe::PerformanceMode::LowLatency); - - #if JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK - if (newCallback != nullptr) - { - stabilizedCallback = std::make_unique (newCallback); - builder.setCallback (stabilizedCallback.get()); - } - #else - builder.setCallback (newCallback); - #endif - - JUCE_OBOE_LOG (String ("Preparing Oboe stream with params:") - + "\nAAudio supported = " + String (int (builder.isAAudioSupported())) - + "\nAPI = " + getOboeString (builder.getAudioApi()) - + "\nDeviceId = " + String (deviceId) - + "\nDirection = " + getOboeString (direction) - + "\nSharingMode = " + getOboeString (sharingMode) - + "\nChannelCount = " + String (channelCount) - + "\nFormat = " + getOboeString (format) - + "\nSampleRate = " + String (newSampleRate) - + "\nPerformanceMode = " + getOboeString (oboe::PerformanceMode::LowLatency)); - - openResult = builder.openStream (&stream); - JUCE_OBOE_LOG ("Building Oboe stream with result: " + getOboeString (openResult) - + "\nStream state = " + (stream != nullptr ? getOboeString (stream->getState()) : String ("?"))); - - if (stream != nullptr && newBufferSize != 0) - { - JUCE_OBOE_LOG ("Setting the bufferSizeInFrames to " + String (newBufferSize)); - stream->setBufferSizeInFrames (newBufferSize); - } - - JUCE_OBOE_LOG (String ("Stream details:") - + "\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?")) - + "\nDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?")) - + "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?")) - + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?")) - + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?")) - + "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?")) - + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?")) - + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?")) - + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?")) - + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?")) - + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?")) - + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?")) - + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?")) - + "\nPerformanceMode = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?"))); - } - - void close() - { - if (stream != nullptr) - { - oboe::Result result = stream->close(); - ignoreUnused (result); - JUCE_OBOE_LOG ("Requested Oboe stream close with result: " + getOboeString (result)); - } - } - - oboe::AudioStream* stream = nullptr; - #if JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK - std::unique_ptr stabilizedCallback; - #endif - oboe::Result openResult; - }; - - //============================================================================== - class OboeSessionBase : protected oboe::AudioStreamCallback - { - public: - static OboeSessionBase* create (OboeAudioIODevice& owner, - int inputDeviceId, int outputDeviceId, - int numInputChannels, int numOutputChannels, - int sampleRate, int bufferSize); - - virtual void start() = 0; - virtual void stop() = 0; - virtual int getOutputLatencyInSamples() = 0; - virtual int getInputLatencyInSamples() = 0; - - bool openedOk() const noexcept - { - if (inputStream != nullptr && ! inputStream->openedOk()) - return false; - - return outputStream != nullptr && outputStream->openedOk(); - } - - int getCurrentBitDepth() const noexcept { return bitDepth; } - - int getXRunCount() const - { - int inputXRunCount = jmax (0, inputStream != nullptr ? inputStream->getXRunCount() : 0); - int outputXRunCount = jmax (0, outputStream != nullptr ? outputStream->getXRunCount() : 0); - - return inputXRunCount + outputXRunCount; - } - - protected: - OboeSessionBase (OboeAudioIODevice& ownerToUse, - int inputDeviceIdToUse, int outputDeviceIdToUse, - int numInputChannelsToUse, int numOutputChannelsToUse, - int sampleRateToUse, int bufferSizeToUse, - oboe::AudioFormat streamFormatToUse, - int bitDepthToUse) - : owner (ownerToUse), - inputDeviceId (inputDeviceIdToUse), - outputDeviceId (outputDeviceIdToUse), - numInputChannels (numInputChannelsToUse), - numOutputChannels (numOutputChannelsToUse), - sampleRate (sampleRateToUse), - bufferSize (bufferSizeToUse), - streamFormat (streamFormatToUse), - bitDepth (bitDepthToUse), - outputStream (new OboeStream (outputDeviceId, - oboe::Direction::Output, - oboe::SharingMode::Exclusive, - numOutputChannels, - streamFormatToUse, - sampleRateToUse, - bufferSizeToUse, - this)) - { - if (numInputChannels > 0) - { - inputStream.reset (new OboeStream (inputDeviceId, - oboe::Direction::Input, - oboe::SharingMode::Exclusive, - numInputChannels, - streamFormatToUse, - sampleRateToUse, - bufferSizeToUse, - nullptr)); - - if (inputStream->openedOk() && outputStream->openedOk()) - { - // Input & output sample rates should match! - jassert (inputStream->getNativeStream()->getSampleRate() - == outputStream->getNativeStream()->getSampleRate()); - } - - checkStreamSetup (inputStream.get(), inputDeviceId, numInputChannels, - sampleRate, bufferSize, streamFormat); - } - - checkStreamSetup (outputStream.get(), outputDeviceId, numOutputChannels, - sampleRate, bufferSize, streamFormat); - } - - // Not strictly required as these should not change, but recommended by Google anyway - void checkStreamSetup (OboeStream* stream, int deviceId, int numChannels, int expectedSampleRate, - int expectedBufferSize, oboe::AudioFormat format) - { - if (auto* nativeStream = stream != nullptr ? stream->getNativeStream() : nullptr) - { - ignoreUnused (deviceId, numChannels, sampleRate, expectedBufferSize); - ignoreUnused (streamFormat, bitDepth); - - jassert (numChannels == 0 || numChannels == nativeStream->getChannelCount()); - jassert (expectedSampleRate == 0 || expectedSampleRate == nativeStream->getSampleRate()); - jassert (format == nativeStream->getFormat()); - } - } - - int getBufferCapacityInFrames (bool forInput) const - { - auto& ptr = forInput ? inputStream : outputStream; - - if (ptr == nullptr || ! ptr->openedOk()) - return 0; - - return ptr->getNativeStream()->getBufferCapacityInFrames(); - } - - OboeAudioIODevice& owner; - int inputDeviceId, outputDeviceId; - int numInputChannels, numOutputChannels; - int sampleRate; - int bufferSize; - oboe::AudioFormat streamFormat; - int bitDepth; - - std::unique_ptr inputStream, outputStream; - }; - - //============================================================================== - template - class OboeSessionImpl : public OboeSessionBase - { - public: - OboeSessionImpl (OboeAudioIODevice& ownerToUse, - int inputDeviceIdIn, int outputDeviceIdIn, - int numInputChannelsToUse, int numOutputChannelsToUse, - int sampleRateToUse, int bufferSizeToUse) - : OboeSessionBase (ownerToUse, - inputDeviceIdIn, outputDeviceIdIn, - numInputChannelsToUse, numOutputChannelsToUse, - sampleRateToUse, bufferSizeToUse, - OboeAudioIODeviceBufferHelpers::oboeAudioFormat(), - OboeAudioIODeviceBufferHelpers::bitDepth()), - inputStreamNativeBuffer (static_cast (numInputChannelsToUse * getBufferCapacityInFrames (true))), - inputStreamSampleBuffer (numInputChannels, getBufferCapacityInFrames (true)), - outputStreamSampleBuffer (numOutputChannels, getBufferCapacityInFrames (false)) - { - } - - void start() override - { - audioCallbackGuard.set (0); - - if (inputStream != nullptr) - inputStream->start(); - - outputStream->start(); - - isInputLatencyDetectionSupported = isLatencyDetectionSupported (inputStream.get()); - isOutputLatencyDetectionSupported = isLatencyDetectionSupported (outputStream.get()); - } - - void stop() override - { - while (! audioCallbackGuard.compareAndSetBool (1, 0)) - Thread::sleep (1); - - inputStream = nullptr; - outputStream = nullptr; - - audioCallbackGuard.set (0); - } - - int getOutputLatencyInSamples() override { return outputLatency; } - int getInputLatencyInSamples() override { return inputLatency; } - - private: - bool isLatencyDetectionSupported (OboeStream* stream) - { - if (stream == nullptr || ! openedOk()) - return false; - - auto result = stream->getNativeStream()->getTimestamp (CLOCK_MONOTONIC, nullptr, nullptr); - return result != oboe::Result::ErrorUnimplemented; - } - - oboe::DataCallbackResult onAudioReady (oboe::AudioStream* stream, void* audioData, int32_t numFrames) override - { - if (audioCallbackGuard.compareAndSetBool (1, 0)) - { - if (stream == nullptr) - return oboe::DataCallbackResult::Stop; - - // only output stream should be the master stream receiving callbacks - jassert (stream->getDirection() == oboe::Direction::Output && stream == outputStream->getNativeStream()); - - // Read input from Oboe - inputStreamSampleBuffer.clear(); - inputStreamNativeBuffer.calloc (static_cast (numInputChannels * bufferSize)); - - if (inputStream != nullptr) - { - auto* nativeInputStream = inputStream->getNativeStream(); - - if (nativeInputStream->getFormat() != oboe::AudioFormat::I16 && nativeInputStream->getFormat() != oboe::AudioFormat::Float) - { - JUCE_OBOE_LOG ("Unsupported input stream audio format: " + getOboeString (nativeInputStream->getFormat())); - jassertfalse; - return oboe::DataCallbackResult::Continue; - } - - auto result = inputStream->getNativeStream()->read (inputStreamNativeBuffer.getData(), numFrames, 0); - - if (result) - { - auto referringDirectlyToOboeData = OboeAudioIODeviceBufferHelpers - ::referAudioBufferDirectlyToOboeIfPossible (inputStreamNativeBuffer.get(), - inputStreamSampleBuffer, - result.value()); - - if (! referringDirectlyToOboeData) - OboeAudioIODeviceBufferHelpers::convertFromOboe (inputStreamNativeBuffer.get(), inputStreamSampleBuffer, result.value()); - } - else - { - JUCE_OBOE_LOG ("Failed to read from input stream: " + getOboeString (result.error())); - } - - if (isInputLatencyDetectionSupported) - inputLatency = getLatencyFor (*inputStream); - } - - // Setup output buffer - auto referringDirectlyToOboeData = OboeAudioIODeviceBufferHelpers - ::referAudioBufferDirectlyToOboeIfPossible (static_cast (audioData), - outputStreamSampleBuffer, - numFrames); - - if (! referringDirectlyToOboeData) - outputStreamSampleBuffer.clear(); - - // Process - // NB: the number of samples read from the input can potentially differ from numFrames. - owner.process (inputStreamSampleBuffer.getArrayOfReadPointers(), numInputChannels, - outputStreamSampleBuffer.getArrayOfWritePointers(), numOutputChannels, - numFrames); - - // Write output to Oboe - if (! referringDirectlyToOboeData) - OboeAudioIODeviceBufferHelpers::convertToOboe (outputStreamSampleBuffer, static_cast (audioData), numFrames); - - if (isOutputLatencyDetectionSupported) - outputLatency = getLatencyFor (*outputStream); - - audioCallbackGuard.set (0); - } - - return oboe::DataCallbackResult::Continue; - } - - void printStreamDebugInfo (oboe::AudioStream* stream) - { - ignoreUnused (stream); - - JUCE_OBOE_LOG ("\nUses AAudio = " + (stream != nullptr ? String ((int) stream->usesAAudio()) : String ("?")) - + "\nDirection = " + (stream != nullptr ? getOboeString (stream->getDirection()) : String ("?")) - + "\nSharingMode = " + (stream != nullptr ? getOboeString (stream->getSharingMode()) : String ("?")) - + "\nChannelCount = " + (stream != nullptr ? String (stream->getChannelCount()) : String ("?")) - + "\nFormat = " + (stream != nullptr ? getOboeString (stream->getFormat()) : String ("?")) - + "\nSampleRate = " + (stream != nullptr ? String (stream->getSampleRate()) : String ("?")) - + "\nBufferSizeInFrames = " + (stream != nullptr ? String (stream->getBufferSizeInFrames()) : String ("?")) - + "\nBufferCapacityInFrames = " + (stream != nullptr ? String (stream->getBufferCapacityInFrames()) : String ("?")) - + "\nFramesPerBurst = " + (stream != nullptr ? String (stream->getFramesPerBurst()) : String ("?")) - + "\nFramesPerCallback = " + (stream != nullptr ? String (stream->getFramesPerCallback()) : String ("?")) - + "\nBytesPerFrame = " + (stream != nullptr ? String (stream->getBytesPerFrame()) : String ("?")) - + "\nBytesPerSample = " + (stream != nullptr ? String (stream->getBytesPerSample()) : String ("?")) - + "\nPerformanceMode = " + (stream != nullptr ? getOboeString (stream->getPerformanceMode()) : String ("?")) - + "\ngetDeviceId = " + (stream != nullptr ? String (stream->getDeviceId()) : String ("?"))); - } - - int getLatencyFor (OboeStream& stream) - { - auto& nativeStream = *stream.getNativeStream(); - - if (auto latency = nativeStream.calculateLatencyMillis()) - return static_cast ((latency.value() * sampleRate) / 1000); - - // Get the time that a known audio frame was presented. - int64_t hardwareFrameIndex = 0; - int64_t hardwareFrameHardwareTime = 0; - - auto result = nativeStream.getTimestamp (CLOCK_MONOTONIC, - &hardwareFrameIndex, - &hardwareFrameHardwareTime); - - if (result != oboe::Result::OK) - return 0; - - // Get counter closest to the app. - const bool isOutput = nativeStream.getDirection() == oboe::Direction::Output; - const int64_t appFrameIndex = isOutput ? nativeStream.getFramesWritten() : nativeStream.getFramesRead(); - - // Assume that the next frame will be processed at the current time - int64_t appFrameAppTime = getCurrentTimeNanos(); - - // Calculate the number of frames between app and hardware - int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex; - - // Calculate the time which the next frame will be or was presented - int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / sampleRate; - int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta; - - // Calculate latency as a difference in time between when the current frame is at the app - // and when it is at the hardware. - auto latencyNanos = isOutput ? (appFrameHardwareTime - appFrameAppTime) : (appFrameAppTime - appFrameHardwareTime); - return static_cast ((latencyNanos * sampleRate) / oboe::kNanosPerSecond); - } - - int64_t getCurrentTimeNanos() - { - timespec time; - - if (clock_gettime (CLOCK_MONOTONIC, &time) < 0) - return -1; - - return time.tv_sec * oboe::kNanosPerSecond + time.tv_nsec; - } - - void onErrorBeforeClose (oboe::AudioStream* stream, oboe::Result error) override - { - ignoreUnused (error); - - // only output stream should be the master stream receiving callbacks - jassert (stream->getDirection() == oboe::Direction::Output); - - JUCE_OBOE_LOG ("Oboe stream onErrorBeforeClose(): " + getOboeString (error)); - printStreamDebugInfo (stream); - } - - void onErrorAfterClose (oboe::AudioStream* stream, oboe::Result error) override - { - // only output stream should be the master stream receiving callbacks - jassert (stream->getDirection() == oboe::Direction::Output); - - JUCE_OBOE_LOG ("Oboe stream onErrorAfterClose(): " + getOboeString (error)); - - if (error == oboe::Result::ErrorDisconnected) - { - if (streamRestartGuard.compareAndSetBool (1, 0)) - { - // Close, recreate, and start the stream, not much use in current one. - // Use default device id, to let the OS pick the best ID (since our was disconnected). - - while (! audioCallbackGuard.compareAndSetBool (1, 0)) - Thread::sleep (1); - - outputStream = nullptr; - outputStream.reset (new OboeStream (oboe::kUnspecified, - oboe::Direction::Output, - oboe::SharingMode::Exclusive, - numOutputChannels, - streamFormat, - sampleRate, - bufferSize, - this)); - - outputStream->start(); - - audioCallbackGuard.set (0); - streamRestartGuard.set (0); - } - } - } - - HeapBlock inputStreamNativeBuffer; - AudioBuffer inputStreamSampleBuffer, - outputStreamSampleBuffer; - Atomic audioCallbackGuard { 0 }, - streamRestartGuard { 0 }; - - bool isInputLatencyDetectionSupported = false; - int inputLatency = -1; - - bool isOutputLatencyDetectionSupported = false; - int outputLatency = -1; - }; - - //============================================================================== - friend class OboeAudioIODeviceType; - friend class OboeRealtimeThread; - - //============================================================================== - int actualBufferSize = 0, sampleRate = 0; - bool deviceOpen = false; - String lastError; - BigInteger activeOutputChans, activeInputChans; - Atomic callback { nullptr }; - - int inputDeviceId; - Array supportedInputSampleRates; - int maxNumInputChannels; - int outputDeviceId; - Array supportedOutputSampleRates; - int maxNumOutputChannels; - - std::unique_ptr session; - - bool running = false; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODevice) -}; - -//============================================================================== -OboeAudioIODevice::OboeSessionBase* OboeAudioIODevice::OboeSessionBase::create (OboeAudioIODevice& owner, - int inputDeviceId, - int outputDeviceId, - int numInputChannels, - int numOutputChannels, - int sampleRate, - int bufferSize) -{ - - std::unique_ptr session; - auto sdkVersion = getAndroidSDKVersion(); - - // SDK versions 21 and higher should natively support floating point... - if (sdkVersion >= 21) - { - session.reset (new OboeSessionImpl (owner, inputDeviceId, outputDeviceId, - numInputChannels, numOutputChannels, sampleRate, bufferSize)); - - // ...however, some devices lie so re-try without floating point - if (session != nullptr && (! session->openedOk())) - session.reset(); - } - - if (session == nullptr) - { - session.reset (new OboeSessionImpl (owner, inputDeviceId, outputDeviceId, - numInputChannels, numOutputChannels, sampleRate, bufferSize)); - - if (session != nullptr && (! session->openedOk())) - session.reset(); - } - - return session.release(); -} - -//============================================================================== -class OboeAudioIODeviceType : public AudioIODeviceType -{ -public: - OboeAudioIODeviceType() - : AudioIODeviceType (OboeAudioIODevice::oboeTypeName) - { - // Not using scanForDevices() to maintain behaviour backwards compatible with older APIs - checkAvailableDevices(); - } - - //============================================================================== - void scanForDevices() override {} - - StringArray getDeviceNames (bool wantInputNames) const override - { - StringArray names; - - for (auto& device : wantInputNames ? inputDevices : outputDevices) - names.add (device.name); - - return names; - } - - int getDefaultDeviceIndex (bool) const override - { - return 0; - } - - int getIndexOfDevice (AudioIODevice* device, bool asInput) const override - { - if (auto oboeDevice = static_cast (device)) - { - auto oboeDeviceId = asInput ? oboeDevice->inputDeviceId - : oboeDevice->outputDeviceId; - - auto& devices = asInput ? inputDevices : outputDevices; - - for (int i = 0; i < devices.size(); ++i) - if (devices.getReference (i).id == oboeDeviceId) - return i; - } - - return -1; - } - - bool hasSeparateInputsAndOutputs() const override { return true; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) override - { - auto outputDeviceInfo = getDeviceInfoForName (outputDeviceName, false); - auto inputDeviceInfo = getDeviceInfoForName (inputDeviceName, true); - - if (outputDeviceInfo.id < 0 && inputDeviceInfo.id < 0) - return nullptr; - - auto& name = outputDeviceInfo.name.isNotEmpty() ? outputDeviceInfo.name - : inputDeviceInfo.name; - - return new OboeAudioIODevice (name, - inputDeviceInfo.id, inputDeviceInfo.sampleRates, - inputDeviceInfo.numChannels, - outputDeviceInfo.id, outputDeviceInfo.sampleRates, - outputDeviceInfo.numChannels); - } - - static bool isOboeAvailable() - { - #if JUCE_USE_ANDROID_OBOE - return true; - #else - return false; - #endif - } - - private: - void checkAvailableDevices() - { - auto sampleRates = OboeAudioIODevice::getDefaultSampleRates(); - - inputDevices .add ({ "System Default (Input)", oboe::kUnspecified, sampleRates, 1 }); - outputDevices.add ({ "System Default (Output)", oboe::kUnspecified, sampleRates, 2 }); - - if (! supportsDevicesInfo()) - return; - - auto* env = getEnv(); - - jclass audioManagerClass = env->FindClass ("android/media/AudioManager"); - - // We should be really entering here only if API supports it. - jassert (audioManagerClass != nullptr); - - if (audioManagerClass == nullptr) - return; - - auto audioManager = LocalRef (env->CallObjectMethod (getAppContext().get(), - AndroidContext.getSystemService, - javaString ("audio").get())); - - static jmethodID getDevicesMethod = env->GetMethodID (audioManagerClass, "getDevices", - "(I)[Landroid/media/AudioDeviceInfo;"); - - static constexpr int allDevices = 3; - auto devices = LocalRef ((jobjectArray) env->CallObjectMethod (audioManager, - getDevicesMethod, - allDevices)); - - const int numDevices = env->GetArrayLength (devices.get()); - - for (int i = 0; i < numDevices; ++i) - { - auto device = LocalRef ((jobject) env->GetObjectArrayElement (devices.get(), i)); - addDevice (device, env); - } - - JUCE_OBOE_LOG ("-----InputDevices:"); - - for (auto& device : inputDevices) - { - ignoreUnused (device); - - JUCE_OBOE_LOG ("name = " << device.name); - JUCE_OBOE_LOG ("id = " << String (device.id)); - JUCE_OBOE_LOG ("sample rates size = " << String (device.sampleRates.size())); - JUCE_OBOE_LOG ("num channels = " + String (device.numChannels)); - } - - JUCE_OBOE_LOG ("-----OutputDevices:"); - - for (auto& device : outputDevices) - { - ignoreUnused (device); - - JUCE_OBOE_LOG ("name = " << device.name); - JUCE_OBOE_LOG ("id = " << String (device.id)); - JUCE_OBOE_LOG ("sample rates size = " << String (device.sampleRates.size())); - JUCE_OBOE_LOG ("num channels = " + String (device.numChannels)); - } - } - - bool supportsDevicesInfo() const - { - static auto result = getAndroidSDKVersion() >= 23; - return result; - } - - void addDevice (const LocalRef& device, JNIEnv* env) - { - auto deviceClass = LocalRef ((jclass) env->FindClass ("android/media/AudioDeviceInfo")); - - jmethodID getProductNameMethod = env->GetMethodID (deviceClass, "getProductName", - "()Ljava/lang/CharSequence;"); - - jmethodID getTypeMethod = env->GetMethodID (deviceClass, "getType", "()I"); - jmethodID getIdMethod = env->GetMethodID (deviceClass, "getId", "()I"); - jmethodID getSampleRatesMethod = env->GetMethodID (deviceClass, "getSampleRates", "()[I"); - jmethodID getChannelCountsMethod = env->GetMethodID (deviceClass, "getChannelCounts", "()[I"); - jmethodID isSourceMethod = env->GetMethodID (deviceClass, "isSource", "()Z"); - - auto deviceTypeString = deviceTypeToString (env->CallIntMethod (device, getTypeMethod)); - - if (deviceTypeString.isEmpty()) // unknown device - return; - - auto name = juceString ((jstring) env->CallObjectMethod (device, getProductNameMethod)) + " " + deviceTypeString; - auto id = env->CallIntMethod (device, getIdMethod); - - auto jSampleRates = LocalRef ((jintArray) env->CallObjectMethod (device, getSampleRatesMethod)); - auto sampleRates = jintArrayToJuceArray (jSampleRates); - - auto jChannelCounts = LocalRef ((jintArray) env->CallObjectMethod (device, getChannelCountsMethod)); - auto channelCounts = jintArrayToJuceArray (jChannelCounts); - int numChannels = channelCounts.isEmpty() ? -1 : channelCounts.getLast(); - - auto isInput = env->CallBooleanMethod (device, isSourceMethod); - auto& devices = isInput ? inputDevices : outputDevices; - - devices.add ({ name, id, sampleRates, numChannels }); - } - - static String deviceTypeToString (int type) - { - switch (type) - { - case 0: return {}; - case 1: return "built-in earphone speaker"; - case 2: return "built-in speaker"; - case 3: return "wired headset"; - case 4: return "wired headphones"; - case 5: return "line analog"; - case 6: return "line digital"; - case 7: return "Bluetooth device typically used for telephony"; - case 8: return "Bluetooth device supporting the A2DP profile"; - case 9: return "HDMI"; - case 10: return "HDMI audio return channel"; - case 11: return "USB device"; - case 12: return "USB accessory"; - case 13: return "DOCK"; - case 14: return "FM"; - case 15: return "built-in microphone"; - case 16: return "FM tuner"; - case 17: return "TV tuner"; - case 18: return "telephony"; - case 19: return "auxiliary line-level connectors"; - case 20: return "IP"; - case 21: return "BUS"; - case 22: return "USB headset"; - case 23: return "hearing aid"; - case 24: return "built-in speaker safe"; - case 25: return {}; - default: jassertfalse; return {}; // type not supported yet, needs to be added! - } - } - - static Array jintArrayToJuceArray (const LocalRef& jArray) - { - auto* env = getEnv(); - - jint* jArrayElems = env->GetIntArrayElements (jArray, nullptr); - int numElems = env->GetArrayLength (jArray); - - Array juceArray; - - for (int s = 0; s < numElems; ++s) - juceArray.add (jArrayElems[s]); - - env->ReleaseIntArrayElements (jArray, jArrayElems, 0); - return juceArray; - } - - struct DeviceInfo - { - String name; - int id = -1; - Array sampleRates; - int numChannels; - }; - - DeviceInfo getDeviceInfoForName (const String& name, bool isInput) - { - if (name.isNotEmpty()) - { - for (auto& device : isInput ? inputDevices : outputDevices) - { - if (device.name == name) - return device; - } - } - - return {}; - } - - Array inputDevices, outputDevices; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OboeAudioIODeviceType) -}; - -const char* const OboeAudioIODevice::oboeTypeName = "Android Oboe"; - -bool isOboeAvailable() { return OboeAudioIODeviceType::isOboeAvailable(); } - -//============================================================================== -class OboeRealtimeThread : private oboe::AudioStreamCallback -{ - using OboeStream = OboeAudioIODevice::OboeStream; - -public: - OboeRealtimeThread() - : testStream (new OboeStream (oboe::kUnspecified, - oboe::Direction::Output, - oboe::SharingMode::Exclusive, - 1, - oboe::AudioFormat::Float, - (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), - OboeAudioIODevice::getNativeBufferSize(), - this)), - formatUsed (oboe::AudioFormat::Float) - { - // Fallback to I16 stream format if Float has not worked - if (! testStream->openedOk()) - { - testStream.reset (new OboeStream (oboe::kUnspecified, - oboe::Direction::Output, - oboe::SharingMode::Exclusive, - 1, - oboe::AudioFormat::I16, - (int) AndroidHighPerformanceAudioHelpers::getNativeSampleRate(), - OboeAudioIODevice::getNativeBufferSize(), - this)); - - formatUsed = oboe::AudioFormat::I16; - } - - parentThreadID = pthread_self(); - - pthread_cond_init (&threadReady, nullptr); - pthread_mutex_init (&threadReadyMutex, nullptr); - } - - bool isOk() const - { - return testStream != nullptr && testStream->openedOk(); - } - - pthread_t startThread (void*(*entry)(void*), void* userPtr) - { - pthread_mutex_lock (&threadReadyMutex); - - threadEntryProc = entry; - threadUserPtr = userPtr; - - testStream->start(); - - pthread_cond_wait (&threadReady, &threadReadyMutex); - pthread_mutex_unlock (&threadReadyMutex); - - return realtimeThreadID; - } - - oboe::DataCallbackResult onAudioReady (oboe::AudioStream*, void*, int32_t) override - { - // When running with OpenSL, the first callback will come on the parent thread. - if (threadEntryProc != nullptr && ! pthread_equal (parentThreadID, pthread_self())) - { - pthread_mutex_lock (&threadReadyMutex); - - realtimeThreadID = pthread_self(); - - pthread_cond_signal (&threadReady); - pthread_mutex_unlock (&threadReadyMutex); - - threadEntryProc (threadUserPtr); - threadEntryProc = nullptr; - - MessageManager::callAsync ([this]() { delete this; }); - - return oboe::DataCallbackResult::Stop; - } - - return oboe::DataCallbackResult::Continue; - } - - void onErrorBeforeClose (oboe::AudioStream*, oboe::Result error) override - { - JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorBeforeClose(): " + getOboeString (error)); - ignoreUnused (error); - jassertfalse; // Should never get here! - } - - void onErrorAfterClose (oboe::AudioStream*, oboe::Result error) override - { - JUCE_OBOE_LOG ("OboeRealtimeThread: Oboe stream onErrorAfterClose(): " + getOboeString (error)); - ignoreUnused (error); - jassertfalse; // Should never get here! - } - -private: - //============================================================================== - void* (*threadEntryProc) (void*) = nullptr; - void* threadUserPtr = nullptr; - - pthread_cond_t threadReady; - pthread_mutex_t threadReadyMutex; - pthread_t parentThreadID, realtimeThreadID; - - std::unique_ptr testStream; - oboe::AudioFormat formatUsed; -}; - -//============================================================================== -pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr); -pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr) -{ - auto thread = std::make_unique(); - - if (! thread->isOk()) - return {}; - - auto threadID = thread->startThread (entry, userPtr); - - // the thread will de-allocate itself - thread.release(); - - return threadID; -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp deleted file mode 100644 index 674f827e7..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ /dev/null @@ -1,1292 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -#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) -#endif - -#ifndef SL_ANDROID_PCM_REPRESENTATION_FLOAT - #define SL_ANDROID_PCM_REPRESENTATION_FLOAT ((SLuint32) 0x00000003) -#endif - -#ifndef SL_ANDROID_RECORDING_PRESET_UNPROCESSED - #define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32) 0x00000005) -#endif - -//============================================================================== -struct PCMDataFormatEx : SLDataFormat_PCM -{ - SLuint32 representation; -}; - -//============================================================================== -template struct IntfIID; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; - -SLInterfaceID_ IntfIID::iid = { 0x79216360, 0xddd7, 0x11db, 0xac16, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x8d97c260, 0xddd4, 0x11db, 0x958f, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x97750f60, 0xddd7, 0x11db, 0x92b1, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0xef0bd9c0, 0xddd7, 0x11db, 0xbf49, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0xc5657aa0, 0xdddb, 0x11db, 0x82f7, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x198e4940, 0xc5d7, 0x11df, 0xa2a6, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x89f6a7e0, 0xbeac, 0x11df, 0x8b5c, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; - -template -static void destroyObject (SLObjectType object) -{ - if (object != nullptr && *object != nullptr) - (*object)->Destroy (object); -} - -struct SLObjectItfFree -{ - void operator() (SLObjectItf obj) const noexcept - { - destroyObject (obj); - } -}; - -//============================================================================== -// Some life-time and type management of OpenSL objects -class SlObjectRef -{ -public: - //============================================================================== - SlObjectRef() noexcept {} - SlObjectRef (const SlObjectRef& obj) noexcept : cb (obj.cb) {} - SlObjectRef (SlObjectRef&& obj) noexcept : cb (std::move (obj.cb)) { obj.cb = nullptr; } - explicit SlObjectRef (SLObjectItf o) : cb (new ControlBlock (o)) {} - - //============================================================================== - SlObjectRef& operator= (const SlObjectRef& r) noexcept { cb = r.cb; return *this; } - SlObjectRef& operator= (SlObjectRef&& r) noexcept { cb = std::move (r.cb); r.cb = nullptr; return *this; } - SlObjectRef& operator= (std::nullptr_t) noexcept { cb = nullptr; return *this; } - - //============================================================================== - const SLObjectItf_* operator*() noexcept { return *cb->ptr.get(); } - SLObjectItf operator->() noexcept { return (cb == nullptr ? nullptr : cb->ptr.get()); } - operator SLObjectItf() noexcept { return (cb == nullptr ? nullptr : cb->ptr.get()); } - - //============================================================================== - bool operator== (nullptr_t) const noexcept { return (cb == nullptr || cb->ptr == nullptr); } - bool operator!= (nullptr_t) const noexcept { return (cb != nullptr && cb->ptr != nullptr); } - -private: - //============================================================================== - struct ControlBlock : ReferenceCountedObject - { - ControlBlock() = default; - ControlBlock (SLObjectItf o) : ptr (o) {} - - std::unique_ptr ptr; - }; - - ReferenceCountedObjectPtr cb; -}; - -template -class SlRef : public SlObjectRef -{ -public: - //============================================================================== - SlRef() noexcept {} - SlRef (const SlRef& r) noexcept : SlObjectRef (r), type (r.type) {} - SlRef (SlRef&& r) noexcept : SlObjectRef (std::move (r)), type (r.type) { r.type = nullptr; } - - //============================================================================== - SlRef& operator= (const SlRef& r) noexcept { SlObjectRef::operator= (r); type = r.type; return *this; } - SlRef& operator= (SlRef&& r) noexcept { SlObjectRef::operator= (std::move (r)); type = r.type; r.type = nullptr; return *this; } - SlRef& operator= (std::nullptr_t) noexcept { SlObjectRef::operator= (nullptr); type = nullptr; return *this; } - - //============================================================================== - T* const operator*() noexcept { return *type; } - T* const* operator->() noexcept { return type; } - operator T* const*() noexcept { return type; } - - //============================================================================== - static SlRef cast (SlObjectRef& base) { return SlRef (base); } - static SlRef cast (SlObjectRef&& base) { return SlRef (std::move (base)); } - -private: - SlRef (SlObjectRef& base) : SlObjectRef (base) - { - if (auto obj = SlObjectRef::operator->()) - { - auto err = (*obj)->GetInterface (obj, &IntfIID::iid, &type); - - if (type != nullptr && err == SL_RESULT_SUCCESS) - return; - } - - *this = nullptr; - } - - SlRef (SlObjectRef&& base) : SlObjectRef (std::move (base)) - { - if (auto obj = SlObjectRef::operator->()) - { - auto err = (*obj)->GetInterface (obj, &IntfIID::iid, &type); - base = nullptr; - - if (type != nullptr && err == SL_RESULT_SUCCESS) - return; - } - - *this = nullptr; - } - - T* const* type = nullptr; -}; - -//============================================================================== -template struct BufferHelpers {}; - -template <> -struct BufferHelpers -{ - enum { isFloatingPoint = 0 }; - - static void initPCMDataFormat (PCMDataFormatEx& dataFormat, int numChannels, double sampleRate) - { - dataFormat.formatType = SL_DATAFORMAT_PCM; - dataFormat.numChannels = (SLuint32) numChannels; - dataFormat.samplesPerSec = (SLuint32) (sampleRate * 1000); - dataFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - dataFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - dataFormat.channelMask = (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : - (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); - dataFormat.endianness = SL_BYTEORDER_LITTLEENDIAN; - dataFormat.representation = 0; - } - - static void prepareCallbackBuffer (AudioBuffer&, int16*) {} - - using LittleEndianInt16 = AudioData::Format; - using NativeFloat32 = AudioData::Format; - - static void convertFromOpenSL (const int16* srcInterleaved, AudioBuffer& audioBuffer) - { - const auto numChannels = audioBuffer.getNumChannels(); - - AudioData::deinterleaveSamples (AudioData::InterleavedSource { reinterpret_cast (srcInterleaved), numChannels }, - AudioData::NonInterleavedDest { audioBuffer.getArrayOfWritePointers(), numChannels }, - audioBuffer.getNumSamples()); - } - - static void convertToOpenSL (const AudioBuffer& audioBuffer, int16* dstInterleaved) - { - const auto numChannels = audioBuffer.getNumChannels(); - - AudioData::interleaveSamples (AudioData::NonInterleavedSource { audioBuffer.getArrayOfReadPointers(), numChannels }, - AudioData::InterleavedDest { reinterpret_cast (dstInterleaved), numChannels }, - audioBuffer.getNumSamples()); - } - -}; - -template <> -struct BufferHelpers -{ - enum { isFloatingPoint = 1 }; - - static void initPCMDataFormat (PCMDataFormatEx& dataFormat, int numChannels, double sampleRate) - { - dataFormat.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - dataFormat.numChannels = (SLuint32) numChannels; - dataFormat.samplesPerSec = (SLuint32) (sampleRate * 1000); - dataFormat.bitsPerSample = 32; - dataFormat.containerSize = 32; - dataFormat.channelMask = (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : - (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); - dataFormat.endianness = SL_BYTEORDER_LITTLEENDIAN; - dataFormat.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; - } - - static void prepareCallbackBuffer (AudioBuffer& audioBuffer, float* native) - { - if (audioBuffer.getNumChannels() == 1) - audioBuffer.setDataToReferTo (&native, 1, audioBuffer.getNumSamples()); - } - - using LittleEndianFloat32 = AudioData::Format; - using NativeFloat32 = AudioData::Format; - - static void convertFromOpenSL (const float* srcInterleaved, AudioBuffer& audioBuffer) - { - const auto numChannels = audioBuffer.getNumChannels(); - - if (numChannels == 1) - { - jassert (srcInterleaved == audioBuffer.getWritePointer (0)); - return; - } - - AudioData::deinterleaveSamples (AudioData::InterleavedSource { srcInterleaved, numChannels }, - AudioData::NonInterleavedDest { audioBuffer.getArrayOfWritePointers(), numChannels }, - audioBuffer.getNumSamples()); - } - - static void convertToOpenSL (const AudioBuffer& audioBuffer, float* dstInterleaved) - { - const auto numChannels = audioBuffer.getNumChannels(); - - if (numChannels == 1) - { - jassert (dstInterleaved == audioBuffer.getReadPointer (0)); - return; - } - - AudioData::interleaveSamples (AudioData::NonInterleavedSource { audioBuffer.getArrayOfReadPointers(), numChannels }, - AudioData::InterleavedDest { dstInterleaved, numChannels }, - audioBuffer.getNumSamples()); - } -}; - -//============================================================================== -using CreateEngineFunc = SLresult (*) (SLObjectItf*, SLuint32, const SLEngineOption*, - SLuint32, const SLInterfaceID*, const SLboolean*); - -struct OpenSLEngineHolder -{ - OpenSLEngineHolder() - { - if (auto createEngine = (CreateEngineFunc) slLibrary.getFunction ("slCreateEngine")) - { - SLObjectItf obj = nullptr; - auto err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr); - - if (err != SL_RESULT_SUCCESS || obj == nullptr || *obj == nullptr - || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - destroyObject (obj); - } - - engine = SlRef::cast (SlObjectRef (obj)); - } - } - - DynamicLibrary slLibrary { "libOpenSLES.so" }; - SlRef engine; -}; - -OpenSLEngineHolder& getEngineHolder() -{ - static OpenSLEngineHolder holder; - return holder; -} - -//============================================================================== -class SLRealtimeThread; - -//============================================================================== -class OpenSLAudioIODevice : public AudioIODevice -{ -public: - //============================================================================== - template - class OpenSLSessionT; - - //============================================================================== - // CRTP - template - struct OpenSLQueueRunner - { - OpenSLQueueRunner (OpenSLSessionT& sessionToUse, int numChannelsToUse) - : owner (sessionToUse), - numChannels (numChannelsToUse), - nativeBuffer (static_cast (numChannels * owner.bufferSize * owner.numBuffers)), - scratchBuffer (numChannelsToUse, owner.bufferSize), - sampleBuffer (scratchBuffer.getArrayOfWritePointers(), numChannelsToUse, owner.bufferSize) - {} - - ~OpenSLQueueRunner() - { - if (config != nullptr && javaProxy != nullptr) - { - javaProxy.clear(); - (*config)->ReleaseJavaProxy (config, /*SL_ANDROID_JAVA_PROXY_ROUTING*/1); - } - } - - bool init() - { - runner = crtp().createPlayerOrRecorder(); - - if (runner == nullptr) - return false; - - const bool supportsJavaProxy = (getAndroidSDKVersion() >= 24); - - if (supportsJavaProxy) - { - // may return nullptr on some platforms - that's ok - config = SlRef::cast (runner); - - if (config != nullptr) - { - jobject audioRoutingJni; - auto status = (*config)->AcquireJavaProxy (config, /*SL_ANDROID_JAVA_PROXY_ROUTING*/1, - &audioRoutingJni); - - if (status == SL_RESULT_SUCCESS && audioRoutingJni != nullptr) - javaProxy = GlobalRef (LocalRef(getEnv()->NewLocalRef (audioRoutingJni))); - } - } - - queue = SlRef::cast (runner); - - if (queue == nullptr) - return false; - - return ((*queue)->RegisterCallback (queue, staticFinished, this) == SL_RESULT_SUCCESS); - } - - void clear() - { - nextBlock.set (0); - numBlocksOut.set (0); - - zeromem (nativeBuffer.get(), static_cast (owner.bufferSize * numChannels * owner.numBuffers) * sizeof (T)); - scratchBuffer.clear(); - (*queue)->Clear (queue); - } - - void enqueueBuffer() - { - (*queue)->Enqueue (queue, getCurrentBuffer(), static_cast (getBufferSizeInSamples() * sizeof (T))); - ++numBlocksOut; - } - - bool isBufferAvailable() const { return (numBlocksOut.get() < owner.numBuffers); } - T* getNextBuffer() { nextBlock.set((nextBlock.get() + 1) % owner.numBuffers); return getCurrentBuffer(); } - T* getCurrentBuffer() { return nativeBuffer.get() + (static_cast (nextBlock.get()) * getBufferSizeInSamples()); } - size_t getBufferSizeInSamples() const { return static_cast (owner.bufferSize * numChannels); } - - void finished (SLAndroidSimpleBufferQueueItf) - { - --numBlocksOut; - owner.doSomeWorkOnAudioThread(); - } - - static void staticFinished (SLAndroidSimpleBufferQueueItf caller, void *pContext) - { - reinterpret_cast (pContext)->finished (caller); - } - - // get the "this" pointer for CRTP - Child& crtp() { return * ((Child*) this); } - const Child& crtp() const { return * ((Child*) this); } - - OpenSLSessionT& owner; - - SlRef runner; - SlRef queue; - SlRef config; - GlobalRef javaProxy; - - int numChannels; - - HeapBlock nativeBuffer; - AudioBuffer scratchBuffer, sampleBuffer; - - Atomic nextBlock { 0 }, numBlocksOut { 0 }; - }; - - //============================================================================== - template - struct OpenSLQueueRunnerPlayer : OpenSLQueueRunner, SLPlayItf_> - { - using Base = OpenSLQueueRunner, SLPlayItf_>; - - OpenSLQueueRunnerPlayer (OpenSLSessionT& sessionToUse, int numChannelsToUse) - : Base (sessionToUse, numChannelsToUse) - {} - - SlRef createPlayerOrRecorder() - { - SLDataLocator_AndroidSimpleBufferQueue queueLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast (Base::owner.numBuffers) }; - SLDataLocator_OutputMix outputMix = { SL_DATALOCATOR_OUTPUTMIX, Base::owner.outputMix }; - - PCMDataFormatEx dataFormat; - BufferHelpers::initPCMDataFormat (dataFormat, Base::numChannels, Base::owner.sampleRate); - - SLDataSource source = { &queueLocator, &dataFormat }; - SLDataSink sink = { &outputMix, nullptr }; - - SLInterfaceID queueInterfaces[] = { &IntfIID::iid, &IntfIID::iid }; - SLboolean interfaceRequired[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE}; - - SLObjectItf obj = nullptr; - - auto& holder = getEngineHolder(); - - if (auto e = *holder.engine) - { - auto status = e->CreateAudioPlayer (holder.engine, &obj, &source, &sink, 2, - queueInterfaces, interfaceRequired); - - if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize(obj, 0) != SL_RESULT_SUCCESS) - { - destroyObject (obj); - return {}; - } - } - - return SlRef::cast (SlObjectRef (obj)); - } - - void setState (bool running) { (*Base::runner)->SetPlayState (Base::runner, running ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_STOPPED); } - }; - - template - struct OpenSLQueueRunnerRecorder : public OpenSLQueueRunner, SLRecordItf_> - { - using Base = OpenSLQueueRunner, SLRecordItf_>; - - OpenSLQueueRunnerRecorder (OpenSLSessionT& sessionToUse, int numChannelsToUse) - : Base (sessionToUse, numChannelsToUse) - {} - - SlRef createPlayerOrRecorder() - { - SLDataLocator_IODevice ioDeviceLocator = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr }; - SLDataLocator_AndroidSimpleBufferQueue queueLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast (Base::owner.numBuffers) }; - - PCMDataFormatEx dataFormat; - BufferHelpers::initPCMDataFormat (dataFormat, Base::numChannels, Base::owner.sampleRate); - - SLDataSource source = { &ioDeviceLocator, nullptr }; - SLDataSink sink = { &queueLocator, &dataFormat }; - - SLInterfaceID queueInterfaces[] = { &IntfIID::iid, &IntfIID::iid }; - SLboolean interfaceRequired[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE }; - - SLObjectItf obj = nullptr; - - auto& holder = getEngineHolder(); - - if (auto e = *holder.engine) - { - auto status = e->CreateAudioRecorder (holder.engine, &obj, &source, &sink, 2, queueInterfaces, interfaceRequired); - - if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - destroyObject (obj); - return {}; - } - } - - return SlRef::cast (SlObjectRef (obj)); - } - - bool setAudioPreprocessingEnabled (bool shouldEnable) - { - if (Base::config != nullptr) - { - const bool supportsUnprocessed = (getAndroidSDKVersion() >= 25); - const SLuint32 recordingPresetValue - = (shouldEnable ? SL_ANDROID_RECORDING_PRESET_GENERIC - : (supportsUnprocessed ? SL_ANDROID_RECORDING_PRESET_UNPROCESSED - : SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION)); - - auto status = (*Base::config)->SetConfiguration (Base::config, SL_ANDROID_KEY_RECORDING_PRESET, - &recordingPresetValue, sizeof (recordingPresetValue)); - - return (status == SL_RESULT_SUCCESS); - } - - return false; - } - - void setState (bool running) { (*Base::runner)->SetRecordState (Base::runner, running ? SL_RECORDSTATE_RECORDING - : SL_RECORDSTATE_STOPPED); } - }; - - //============================================================================== - class OpenSLSession - { - public: - OpenSLSession (int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse) - : inputChannels (numInputChannels), outputChannels (numOutputChannels), - sampleRate (samleRateToUse), bufferSize (bufferSizeToUse), numBuffers (numBuffersToUse) - { - jassert (numInputChannels > 0 || numOutputChannels > 0); - - if (outputChannels > 0) - { - auto& holder = getEngineHolder(); - SLObjectItf obj = nullptr; - - auto err = (*holder.engine)->CreateOutputMix (holder.engine, &obj, 0, nullptr, nullptr); - - if (err != SL_RESULT_SUCCESS || obj == nullptr || *obj == nullptr - || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - destroyObject (obj); - return; - } - - outputMix = SlRef::cast (SlObjectRef (obj)); - } - } - - virtual ~OpenSLSession() {} - - virtual bool openedOK() const { return (outputChannels == 0 || outputMix != nullptr); } - virtual void start() { stop(); jassert (callback.get() != nullptr); running = true; } - virtual void stop() { running = false; } - - virtual bool setAudioPreprocessingEnabled (bool shouldEnable) = 0; - virtual bool supportsFloatingPoint() const noexcept = 0; - virtual int getXRunCount() const noexcept = 0; - - void setCallback (AudioIODeviceCallback* callbackToUse) - { - if (! running) - { - callback.set (callbackToUse); - return; - } - - // don't set callback to null! stop the playback instead! - jassert (callbackToUse != nullptr); - - // spin-lock until we can set the callback - for (;;) - { - auto old = callback.get(); - - if (old == callbackToUse) - break; - - if (callback.compareAndSetBool (callbackToUse, old)) - break; - - Thread::sleep (1); - } - } - - void process (const float** inputChannelData, float** outputChannelData) - { - if (auto* cb = callback.exchange (nullptr)) - { - cb->audioDeviceIOCallbackWithContext (inputChannelData, inputChannels, outputChannelData, outputChannels, bufferSize, {}); - callback.set (cb); - } - else - { - for (int i = 0; i < outputChannels; ++i) - zeromem (outputChannelData[i], sizeof(float) * static_cast (bufferSize)); - } - } - - static OpenSLSession* create (int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse); - - //============================================================================== - int inputChannels, outputChannels; - double sampleRate; - int bufferSize, numBuffers; - bool running = false, audioProcessingEnabled = true; - - SlRef outputMix; - - Atomic callback { nullptr }; - }; - - template - class OpenSLSessionT : public OpenSLSession - { - public: - OpenSLSessionT (int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse) - : OpenSLSession (numInputChannels, numOutputChannels, - samleRateToUse, bufferSizeToUse, numBuffersToUse) - { - jassert (numInputChannels > 0 || numOutputChannels > 0); - - if (OpenSLSession::openedOK()) - { - if (inputChannels > 0) - { - recorder.reset (new OpenSLQueueRunnerRecorder (*this, inputChannels)); - - if (! recorder->init()) - { - recorder = nullptr; - return; - } - } - - if (outputChannels > 0) - { - player.reset (new OpenSLQueueRunnerPlayer (*this, outputChannels)); - - if (! player->init()) - { - player = nullptr; - return; - } - - const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24); - getUnderrunCount = supportsUnderrunCount ? getEnv()->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : nullptr; - } - } - } - - bool openedOK() const override - { - return OpenSLSession::openedOK() && (inputChannels == 0 || recorder != nullptr) - && (outputChannels == 0 || player != nullptr); - } - - void start() override - { - OpenSLSession::start(); - - guard.set (0); - - if (inputChannels > 0) - recorder->clear(); - - if (outputChannels > 0) - player->clear(); - - // first enqueue all buffers - for (int i = 0; i < numBuffers; ++i) - doSomeWorkOnAudioThread(); - - if (inputChannels > 0) - recorder->setState (true); - - if (outputChannels > 0) - player->setState (true); - } - - void stop() override - { - OpenSLSession::stop(); - - while (! guard.compareAndSetBool (1, 0)) - Thread::sleep (1); - - if (inputChannels > 0) - recorder->setState (false); - - if (outputChannels > 0) - player->setState (false); - - guard.set (0); - } - - bool setAudioPreprocessingEnabled (bool shouldEnable) override - { - if (shouldEnable != audioProcessingEnabled) - { - audioProcessingEnabled = shouldEnable; - - if (recorder != nullptr) - return recorder->setAudioPreprocessingEnabled (audioProcessingEnabled); - } - - return true; - } - - int getXRunCount() const noexcept override - { - if (player != nullptr && player->javaProxy != nullptr && getUnderrunCount != nullptr) - return getEnv()->CallIntMethod (player->javaProxy, getUnderrunCount); - - return -1; - } - - bool supportsFloatingPoint() const noexcept override { return (BufferHelpers::isFloatingPoint != 0); } - - void doSomeWorkOnAudioThread() - { - // only the player or the recorder should enter this section at any time - if (guard.compareAndSetBool (1, 0)) - { - // are there enough buffers available to process some audio - if ((inputChannels == 0 || recorder->isBufferAvailable()) && (outputChannels == 0 || player->isBufferAvailable())) - { - T* recorderBuffer = (inputChannels > 0 ? recorder->getNextBuffer() : nullptr); - T* playerBuffer = (outputChannels > 0 ? player->getNextBuffer() : nullptr); - - const float** inputChannelData = nullptr; - float** outputChannelData = nullptr; - - if (recorderBuffer != nullptr) - { - BufferHelpers::prepareCallbackBuffer (recorder->sampleBuffer, recorderBuffer); - BufferHelpers::convertFromOpenSL (recorderBuffer, recorder->sampleBuffer); - - inputChannelData = recorder->sampleBuffer.getArrayOfReadPointers(); - } - - if (playerBuffer != nullptr) - { - BufferHelpers::prepareCallbackBuffer (player->sampleBuffer, playerBuffer); - outputChannelData = player->sampleBuffer.getArrayOfWritePointers(); - } - - process (inputChannelData, outputChannelData); - - if (recorderBuffer != nullptr) - recorder->enqueueBuffer(); - - if (playerBuffer != nullptr) - { - BufferHelpers::convertToOpenSL (player->sampleBuffer, playerBuffer); - player->enqueueBuffer(); - } - } - - guard.set (0); - } - } - - //============================================================================== - std::unique_ptr> player; - std::unique_ptr> recorder; - Atomic guard; - jmethodID getUnderrunCount = nullptr; - }; - - //============================================================================== - OpenSLAudioIODevice (const String& deviceName) : AudioIODevice (deviceName, openSLTypeName) - { - // OpenSL has piss-poor support for determining latency, so the only way I can find to - // get a number for this is by asking the AudioTrack/AudioRecord classes.. - AndroidAudioIODevice javaDevice (deviceName); - - // this is a total guess about how to calculate the latency, but seems to vaguely agree - // with the devices I've tested.. YMMV - inputLatency = (javaDevice.minBufferSizeIn * 2) / 3; - outputLatency = (javaDevice.minBufferSizeOut * 2) / 3; - - const int64 longestLatency = jmax (inputLatency, outputLatency); - const int64 totalLatency = inputLatency + outputLatency; - inputLatency = (int) ((longestLatency * inputLatency) / totalLatency) & ~15; - outputLatency = (int) ((longestLatency * outputLatency) / totalLatency) & ~15; - - // You can only create this class if you are sure that your hardware supports OpenSL - jassert (getEngineHolder().slLibrary.getNativeHandle() != nullptr); - } - - ~OpenSLAudioIODevice() override - { - close(); - } - - bool openedOk() const { return session != nullptr; } - - StringArray getOutputChannelNames() override - { - StringArray s; - s.add ("Left"); - s.add ("Right"); - return s; - } - - StringArray getInputChannelNames() override - { - StringArray s; - s.add ("Audio Input"); - return s; - } - - Array getAvailableSampleRates() override - { - // see https://developer.android.com/ndk/guides/audio/opensl-for-android.html - - static const double rates[] = { 8000.0, 11025.0, 12000.0, 16000.0, - 22050.0, 24000.0, 32000.0, 44100.0, 48000.0 }; - - Array retval (rates, numElementsInArray (rates)); - - // make sure the native sample rate is part of the list - double native = AndroidHighPerformanceAudioHelpers::getNativeSampleRate(); - - if (native != 0.0 && ! retval.contains (native)) - retval.add (native); - - return retval; - } - - Array getAvailableBufferSizes() override - { - return AndroidHighPerformanceAudioHelpers::getAvailableBufferSizes (AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(), - getAvailableSampleRates()); - } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double requestedSampleRate, - int bufferSize) override - { - close(); - - lastError.clear(); - - sampleRate = (int) (requestedSampleRate > 0 ? requestedSampleRate : AndroidHighPerformanceAudioHelpers::getNativeSampleRate()); - auto preferredBufferSize = (bufferSize > 0) ? bufferSize : getDefaultBufferSize(); - - audioBuffersToEnqueue = [this, preferredBufferSize] - { - using namespace AndroidHighPerformanceAudioHelpers; - - auto nativeBufferSize = getNativeBufferSizeHint(); - - if (canUseHighPerformanceAudioPath (nativeBufferSize, preferredBufferSize, sampleRate)) - return preferredBufferSize / nativeBufferSize; - - - return 1; - }(); - - actualBufferSize = preferredBufferSize / audioBuffersToEnqueue; - - jassert ((actualBufferSize * audioBuffersToEnqueue) == preferredBufferSize); - - activeOutputChans = outputChannels; - activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); - auto numOutputChannels = activeOutputChans.countNumberOfSetBits(); - - activeInputChans = inputChannels; - activeInputChans.setRange (1, activeInputChans.getHighestBit(), false); - auto numInputChannels = activeInputChans.countNumberOfSetBits(); - - if (numInputChannels > 0 && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))) - { - // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio - // before trying to open an audio input device. This is not going to work! - jassertfalse; - lastError = "Error opening OpenSL input device: the app was not granted android.permission.RECORD_AUDIO"; - } - - session.reset (OpenSLSession::create (numInputChannels, numOutputChannels, - sampleRate, actualBufferSize, audioBuffersToEnqueue)); - if (session != nullptr) - { - session->setAudioPreprocessingEnabled (audioProcessingEnabled); - } - else - { - if (numInputChannels > 0 && numOutputChannels > 0 && RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) - { - // New versions of the Android emulator do not seem to support audio input anymore on OS X - activeInputChans = BigInteger(0); - numInputChannels = 0; - - session.reset (OpenSLSession::create (numInputChannels, numOutputChannels, - sampleRate, actualBufferSize, audioBuffersToEnqueue)); - } - } - - DBG ("OpenSL: numInputChannels = " << numInputChannels - << ", numOutputChannels = " << numOutputChannels - << ", nativeBufferSize = " << AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint() - << ", nativeSampleRate = " << AndroidHighPerformanceAudioHelpers::getNativeSampleRate() - << ", actualBufferSize = " << actualBufferSize - << ", audioBuffersToEnqueue = " << audioBuffersToEnqueue - << ", sampleRate = " << sampleRate - << ", supportsFloatingPoint = " << (session != nullptr && session->supportsFloatingPoint() ? "true" : "false")); - - if (session == nullptr) - lastError = "Unknown error initializing opensl session"; - - deviceOpen = (session != nullptr); - return lastError; - } - - void close() override - { - stop(); - session = nullptr; - callback = nullptr; - } - - int getOutputLatencyInSamples() override { return outputLatency; } - int getInputLatencyInSamples() override { return inputLatency; } - bool isOpen() override { return deviceOpen; } - int getCurrentBufferSizeSamples() override { return actualBufferSize * audioBuffersToEnqueue; } - int getCurrentBitDepth() override { return (session != nullptr && session->supportsFloatingPoint() ? 32 : 16); } - BigInteger getActiveOutputChannels() const override { return activeOutputChans; } - BigInteger getActiveInputChannels() const override { return activeInputChans; } - String getLastError() override { return lastError; } - bool isPlaying() override { return callback != nullptr; } - int getXRunCount() const noexcept override { return (session != nullptr ? session->getXRunCount() : -1); } - - int getDefaultBufferSize() override - { - return AndroidHighPerformanceAudioHelpers::getDefaultBufferSize (AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(), - getCurrentSampleRate()); - } - - double getCurrentSampleRate() override - { - return (sampleRate == 0.0 ? AndroidHighPerformanceAudioHelpers::getNativeSampleRate() : sampleRate); - } - - void start (AudioIODeviceCallback* newCallback) override - { - if (session != nullptr && callback != newCallback) - { - auto oldCallback = callback; - - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (this); - - if (oldCallback != nullptr) - { - // already running - if (newCallback == nullptr) - stop(); - else - session->setCallback (newCallback); - - oldCallback->audioDeviceStopped(); - } - else - { - jassert (newCallback != nullptr); - - // session hasn't started yet - session->setCallback (newCallback); - session->start(); - } - - callback = newCallback; - } - } - - void stop() override - { - if (session != nullptr && callback != nullptr) - { - callback = nullptr; - session->stop(); - session->setCallback (nullptr); - } - } - - bool setAudioPreprocessingEnabled (bool shouldAudioProcessingBeEnabled) override - { - audioProcessingEnabled = shouldAudioProcessingBeEnabled; - - if (session != nullptr) - session->setAudioPreprocessingEnabled (audioProcessingEnabled); - - return true; - } - - static const char* const openSLTypeName; - -private: - //============================================================================== - friend class SLRealtimeThread; - - //============================================================================== - int actualBufferSize = 0, sampleRate = 0, audioBuffersToEnqueue = 0; - int inputLatency, outputLatency; - bool deviceOpen = false, audioProcessingEnabled = true; - String lastError; - BigInteger activeOutputChans, activeInputChans; - AudioIODeviceCallback* callback = nullptr; - - std::unique_ptr session; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioIODevice) -}; - -OpenSLAudioIODevice::OpenSLSession* OpenSLAudioIODevice::OpenSLSession::create (int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse) -{ - std::unique_ptr retval; - auto sdkVersion = getAndroidSDKVersion(); - - // SDK versions 21 and higher should natively support floating point... - if (sdkVersion >= 21) - { - retval.reset (new OpenSLSessionT (numInputChannels, numOutputChannels, samleRateToUse, - bufferSizeToUse, numBuffersToUse)); - - // ...however, some devices lie so re-try without floating point - if (retval != nullptr && (! retval->openedOK())) - retval = nullptr; - } - - if (retval == nullptr) - { - retval.reset (new OpenSLSessionT (numInputChannels, numOutputChannels, samleRateToUse, - bufferSizeToUse, numBuffersToUse)); - - if (retval != nullptr && (! retval->openedOK())) - retval = nullptr; - } - - return retval.release(); -} - -//============================================================================== -class OpenSLAudioDeviceType : public AudioIODeviceType -{ -public: - OpenSLAudioDeviceType() : AudioIODeviceType (OpenSLAudioIODevice::openSLTypeName) {} - - //============================================================================== - void scanForDevices() override {} - - StringArray getDeviceNames (bool) const override { return StringArray (OpenSLAudioIODevice::openSLTypeName); } - int getDefaultDeviceIndex (bool) const override { return 0; } - int getIndexOfDevice (AudioIODevice* device, bool) const override { return device != nullptr ? 0 : -1; } - bool hasSeparateInputsAndOutputs() const override { return false; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) override - { - std::unique_ptr dev; - - if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) - dev.reset (new OpenSLAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName - : inputDeviceName)); - - return dev.release(); - } - - static bool isOpenSLAvailable() - { - DynamicLibrary library; - return library.open ("libOpenSLES.so"); - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioDeviceType) -}; - -const char* const OpenSLAudioIODevice::openSLTypeName = "Android OpenSL"; - - -//============================================================================== -bool isOpenSLAvailable() { return OpenSLAudioDeviceType::isOpenSLAvailable(); } - -//============================================================================== -class SLRealtimeThread -{ -public: - static constexpr int numBuffers = 4; - - SLRealtimeThread() - { - if (auto createEngine = (CreateEngineFunc) slLibrary.getFunction ("slCreateEngine")) - { - SLObjectItf obj = nullptr; - auto err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr); - - if (err != SL_RESULT_SUCCESS || obj == nullptr || *obj == nullptr) - return; - - if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - destroyObject (obj); - return; - } - - engine = SlRef::cast (SlObjectRef (obj)); - - if (engine == nullptr) - { - destroyObject (obj); - return; - } - - obj = nullptr; - err = (*engine)->CreateOutputMix (engine, &obj, 0, nullptr, nullptr); - - if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - destroyObject (obj); - return; - } - - outputMix = SlRef::cast (SlObjectRef (obj)); - - if (outputMix == nullptr) - { - destroyObject (obj); - return; - } - - SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast (numBuffers)}; - SLDataLocator_OutputMix outputMixLocator = {SL_DATALOCATOR_OUTPUTMIX, outputMix}; - - PCMDataFormatEx dataFormat; - BufferHelpers::initPCMDataFormat (dataFormat, 1, AndroidHighPerformanceAudioHelpers::getNativeSampleRate()); - - SLDataSource source = { &queueLocator, &dataFormat }; - SLDataSink sink = { &outputMixLocator, nullptr }; - - SLInterfaceID queueInterfaces[] = { &IntfIID::iid }; - SLboolean trueFlag = SL_BOOLEAN_TRUE; - - obj = nullptr; - err = (*engine)->CreateAudioPlayer (engine, &obj, &source, &sink, 1, queueInterfaces, &trueFlag); - - if (err != SL_RESULT_SUCCESS || obj == nullptr) - return; - - if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - destroyObject (obj); - return; - } - - player = SlRef::cast (SlObjectRef (obj)); - - if (player == nullptr) - { - destroyObject (obj); - return; - } - - queue = SlRef::cast (player); - if (queue == nullptr) - return; - - if ((*queue)->RegisterCallback (queue, staticFinished, this) != SL_RESULT_SUCCESS) - { - queue = nullptr; - return; - } - - pthread_cond_init (&threadReady, nullptr); - pthread_mutex_init (&threadReadyMutex, nullptr); - } - } - - bool isOk() const { return queue != nullptr; } - - pthread_t startThread (void* (*entry) (void*), void* userPtr) - { - memset (buffer.get(), 0, static_cast (sizeof (int16) * static_cast (bufferSize * numBuffers))); - - for (int i = 0; i < numBuffers; ++i) - { - int16* dst = buffer.get() + (bufferSize * i); - (*queue)->Enqueue (queue, dst, static_cast (static_cast (bufferSize) * sizeof (int16))); - } - - pthread_mutex_lock (&threadReadyMutex); - - threadEntryProc = entry; - threadUserPtr = userPtr; - - (*player)->SetPlayState (player, SL_PLAYSTATE_PLAYING); - - pthread_cond_wait (&threadReady, &threadReadyMutex); - pthread_mutex_unlock (&threadReadyMutex); - - return threadID; - } - - void finished() - { - if (threadEntryProc != nullptr) - { - pthread_mutex_lock (&threadReadyMutex); - - threadID = pthread_self(); - - pthread_cond_signal (&threadReady); - pthread_mutex_unlock (&threadReadyMutex); - - threadEntryProc (threadUserPtr); - threadEntryProc = nullptr; - - (*player)->SetPlayState (player, SL_PLAYSTATE_STOPPED); - MessageManager::callAsync ([this]() { delete this; }); - } - } - -private: - //============================================================================== - static void staticFinished (SLAndroidSimpleBufferQueueItf, void* context) - { - static_cast (context)->finished(); - } - - //============================================================================== - DynamicLibrary slLibrary { "libOpenSLES.so" }; - - SlRef engine; - SlRef outputMix; - SlRef player; - SlRef queue; - - int bufferSize = AndroidHighPerformanceAudioHelpers::getNativeBufferSizeHint(); - HeapBlock buffer { HeapBlock (static_cast (1 * bufferSize * numBuffers)) }; - - void* (*threadEntryProc) (void*) = nullptr; - void* threadUserPtr = nullptr; - - pthread_cond_t threadReady; - pthread_mutex_t threadReadyMutex; - pthread_t threadID; -}; - -//============================================================================== -pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr); -pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr) -{ - auto thread = std::make_unique(); - - if (! thread->isOk()) - return {}; - - auto threadID = thread->startThread (entry, userPtr); - - // the thread will de-allocate itself - thread.release(); - - return threadID; -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp deleted file mode 100644 index 2e162685e..000000000 --- a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ /dev/null @@ -1,1495 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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. - - ============================================================================== -*/ - -#include - -namespace juce -{ - -class iOSAudioIODevice; - -constexpr const char* const iOSAudioDeviceName = "iOS Audio"; - -#ifndef JUCE_IOS_AUDIO_EXPLICIT_SAMPLERATES - #define JUCE_IOS_AUDIO_EXPLICIT_SAMPLERATES -#endif - -constexpr std::initializer_list iOSExplicitSampleRates { JUCE_IOS_AUDIO_EXPLICIT_SAMPLERATES }; - -//============================================================================== -struct AudioSessionHolder -{ - AudioSessionHolder(); - ~AudioSessionHolder(); - - void handleStatusChange (bool enabled, const char* reason) const; - void handleRouteChange (AVAudioSessionRouteChangeReason reason); - - Array activeDevices; - Array activeDeviceTypes; - - id nativeSession; -}; - -static const char* getRoutingChangeReason (AVAudioSessionRouteChangeReason reason) noexcept -{ - switch (reason) - { - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: return "New device available"; - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: return "Old device unavailable"; - case AVAudioSessionRouteChangeReasonCategoryChange: return "Category change"; - case AVAudioSessionRouteChangeReasonOverride: return "Override"; - case AVAudioSessionRouteChangeReasonWakeFromSleep: return "Wake from sleep"; - case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: return "No suitable route for category"; - case AVAudioSessionRouteChangeReasonRouteConfigurationChange: return "Route configuration change"; - case AVAudioSessionRouteChangeReasonUnknown: - default: return "Unknown"; - } -} - -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmissing-prototypes") - -bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept -{ - if (notification != nil) - { - if (NSDictionary* userInfo = [notification userInfo]) - { - if (NSNumber* number = [userInfo objectForKey: key]) - { - value = [number unsignedIntegerValue]; - return true; - } - } - } - - jassertfalse; - return false; -} - -JUCE_END_IGNORE_WARNINGS_GCC_LIKE - -} // namespace juce - -//============================================================================== -@interface iOSAudioSessionNative : NSObject -{ -@private - juce::AudioSessionHolder* audioSessionHolder; -}; - -- (id) init: (juce::AudioSessionHolder*) holder; -- (void) dealloc; - -- (void) audioSessionChangedInterruptionType: (NSNotification*) notification; -- (void) handleMediaServicesReset; -- (void) handleMediaServicesLost; -- (void) handleRouteChange: (NSNotification*) notification; -@end - -@implementation iOSAudioSessionNative - -- (id) init: (juce::AudioSessionHolder*) holder -{ - self = [super init]; - - if (self != nil) - { - audioSessionHolder = holder; - - auto session = [AVAudioSession sharedInstance]; - auto centre = [NSNotificationCenter defaultCenter]; - - [centre addObserver: self - selector: @selector (audioSessionChangedInterruptionType:) - name: AVAudioSessionInterruptionNotification - object: session]; - - [centre addObserver: self - selector: @selector (handleMediaServicesLost) - name: AVAudioSessionMediaServicesWereLostNotification - object: session]; - - [centre addObserver: self - selector: @selector (handleMediaServicesReset) - name: AVAudioSessionMediaServicesWereResetNotification - object: session]; - - [centre addObserver: self - selector: @selector (handleRouteChange:) - name: AVAudioSessionRouteChangeNotification - object: session]; - } - else - { - jassertfalse; - } - - return self; -} - -- (void) dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver: self]; - [super dealloc]; -} - -- (void) audioSessionChangedInterruptionType: (NSNotification*) notification -{ - NSUInteger value; - - if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value)) - { - switch ((AVAudioSessionInterruptionType) value) - { - case AVAudioSessionInterruptionTypeBegan: - audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan"); - break; - - case AVAudioSessionInterruptionTypeEnded: - audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded"); - break; - - // No default so the code doesn't compile if this enum is extended. - } - } -} - -- (void) handleMediaServicesReset -{ - audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification"); -} - -- (void) handleMediaServicesLost -{ - audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification"); -} - -- (void) handleRouteChange: (NSNotification*) notification -{ - NSUInteger value; - - if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value)) - audioSessionHolder->handleRouteChange ((AVAudioSessionRouteChangeReason) value); -} - -@end - -//============================================================================== -#if JUCE_MODULE_AVAILABLE_juce_graphics - #include -#endif - -namespace juce { - -#ifndef JUCE_IOS_AUDIO_LOGGING - #define JUCE_IOS_AUDIO_LOGGING 0 -#endif - -#if JUCE_IOS_AUDIO_LOGGING - #define JUCE_IOS_AUDIO_LOG(x) DBG(x) -#else - #define JUCE_IOS_AUDIO_LOG(x) -#endif - -static void logNSError (NSError* e) -{ - if (e != nil) - { - JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]); - jassertfalse; - } -} - -#define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); } - -//============================================================================== -class iOSAudioIODeviceType : public AudioIODeviceType, - public AsyncUpdater -{ -public: - iOSAudioIODeviceType(); - ~iOSAudioIODeviceType() override; - - void scanForDevices() override; - StringArray getDeviceNames (bool) const override; - int getDefaultDeviceIndex (bool) const override; - int getIndexOfDevice (AudioIODevice*, bool) const override; - bool hasSeparateInputsAndOutputs() const override; - AudioIODevice* createDevice (const String&, const String&) override; - -private: - void handleRouteChange (AVAudioSessionRouteChangeReason); - - void handleAsyncUpdate() override; - - friend struct AudioSessionHolder; - friend struct iOSAudioIODevice::Pimpl; - - SharedResourcePointer sessionHolder; - - JUCE_DECLARE_WEAK_REFERENCEABLE (iOSAudioIODeviceType) - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType) -}; - -//============================================================================== -struct iOSAudioIODevice::Pimpl : public AudioPlayHead, - public AsyncUpdater -{ - Pimpl (iOSAudioIODeviceType* ioDeviceType, iOSAudioIODevice& ioDevice) - : deviceType (ioDeviceType), - owner (ioDevice) - { - JUCE_IOS_AUDIO_LOG ("Creating iOS audio device"); - - // We need to activate the audio session here to obtain the available sample rates and buffer sizes, - // but if we don't set a category first then background audio will always be stopped. This category - // may be changed later. - setAudioSessionCategory (AVAudioSessionCategoryPlayAndRecord); - - setAudioSessionActive (true); - updateHardwareInfo(); - channelData.reconfigure ({}, {}); - setAudioSessionActive (false); - - sessionHolder->activeDevices.add (this); - } - - ~Pimpl() override - { - sessionHolder->activeDevices.removeFirstMatchingValue (this); - - close(); - } - - static void setAudioSessionCategory (NSString* category) - { - NSUInteger options = 0; - - #if ! JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS - options |= AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers - #endif - - if (category == AVAudioSessionCategoryPlayAndRecord) - { - options |= (AVAudioSessionCategoryOptionDefaultToSpeaker - | AVAudioSessionCategoryOptionAllowBluetooth); - - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - if (@available (iOS 10.0, *)) - options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; - #endif - } - - JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setCategory: category - withOptions: options - error: &error]); - } - - static void setAudioSessionActive (bool enabled) - { - JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled - error: &error]); - } - - int getBufferSize (const double currentSampleRate) - { - return roundToInt (currentSampleRate * [AVAudioSession sharedInstance].IOBufferDuration); - } - - int tryBufferSize (const double currentSampleRate, const int newBufferSize) - { - NSTimeInterval bufferDuration = currentSampleRate > 0 ? (NSTimeInterval) ((newBufferSize + 1) / currentSampleRate) : 0.0; - - auto session = [AVAudioSession sharedInstance]; - JUCE_NSERROR_CHECK ([session setPreferredIOBufferDuration: bufferDuration - error: &error]); - - return getBufferSize (currentSampleRate); - } - - void updateAvailableBufferSizes() - { - availableBufferSizes.clear(); - - auto newBufferSize = tryBufferSize (sampleRate, 64); - jassert (newBufferSize > 0); - - const auto longestBufferSize = tryBufferSize (sampleRate, 4096); - - while (newBufferSize <= longestBufferSize) - { - availableBufferSizes.add (newBufferSize); - newBufferSize *= 2; - } - - // Sometimes the largest supported buffer size is not a power of 2 - availableBufferSizes.addIfNotAlreadyThere (longestBufferSize); - - bufferSize = tryBufferSize (sampleRate, bufferSize); - - #if JUCE_IOS_AUDIO_LOGGING - { - String info ("Available buffer sizes:"); - - for (auto size : availableBufferSizes) - info << " " << size; - - JUCE_IOS_AUDIO_LOG (info); - } - #endif - - JUCE_IOS_AUDIO_LOG ("Buffer size after detecting available buffer sizes: " << bufferSize); - } - - double trySampleRate (double rate) - { - auto session = [AVAudioSession sharedInstance]; - JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate - error: &error]); - - return session.sampleRate; - } - - // Important: the supported audio sample rates change on the iPhone 6S - // depending on whether the headphones are plugged in or not! - void updateAvailableSampleRates() - { - if (iOSExplicitSampleRates.size() != 0) - { - availableSampleRates = Array (iOSExplicitSampleRates); - return; - } - - availableSampleRates.clear(); - - AudioUnitRemovePropertyListenerWithUserData (audioUnit, - kAudioUnitProperty_StreamFormat, - dispatchAudioUnitPropertyChange, - this); - - const double lowestRate = trySampleRate (4000); - availableSampleRates.add (lowestRate); - const double highestRate = trySampleRate (192000); - - JUCE_IOS_AUDIO_LOG ("Lowest supported sample rate: " << lowestRate); - JUCE_IOS_AUDIO_LOG ("Highest supported sample rate: " << highestRate); - - for (double rate = lowestRate + 1000; rate < highestRate; rate += 1000) - { - const double supportedRate = trySampleRate (rate); - JUCE_IOS_AUDIO_LOG ("Trying a sample rate of " << rate << ", got " << supportedRate); - availableSampleRates.addIfNotAlreadyThere (supportedRate); - rate = jmax (rate, supportedRate); - } - - availableSampleRates.addIfNotAlreadyThere (highestRate); - - // Restore the original values. - sampleRate = trySampleRate (sampleRate); - bufferSize = tryBufferSize (sampleRate, bufferSize); - - AudioUnitAddPropertyListener (audioUnit, - kAudioUnitProperty_StreamFormat, - dispatchAudioUnitPropertyChange, - this); - - // Check the current stream format in case things have changed whilst we - // were going through the sample rates - handleStreamFormatChange(); - - #if JUCE_IOS_AUDIO_LOGGING - { - String info ("Available sample rates:"); - - for (auto rate : availableSampleRates) - info << " " << rate; - - JUCE_IOS_AUDIO_LOG (info); - } - #endif - - JUCE_IOS_AUDIO_LOG ("Sample rate after detecting available sample rates: " << sampleRate); - } - - void updateHardwareInfo (bool forceUpdate = false) - { - if (! forceUpdate && ! hardwareInfoNeedsUpdating.compareAndSetBool (false, true)) - return; - - JUCE_IOS_AUDIO_LOG ("Updating hardware info"); - - updateAvailableSampleRates(); - updateAvailableBufferSizes(); - - if (deviceType != nullptr) - deviceType->callDeviceChangeListeners(); - } - - void setTargetSampleRateAndBufferSize() - { - JUCE_IOS_AUDIO_LOG ("Setting target sample rate: " << targetSampleRate); - sampleRate = trySampleRate (targetSampleRate); - JUCE_IOS_AUDIO_LOG ("Actual sample rate: " << sampleRate); - - JUCE_IOS_AUDIO_LOG ("Setting target buffer size: " << targetBufferSize); - bufferSize = tryBufferSize (sampleRate, targetBufferSize); - JUCE_IOS_AUDIO_LOG ("Actual buffer size: " << bufferSize); - } - - String open (const BigInteger& inputChannelsWanted, - const BigInteger& outputChannelsWanted, - double sampleRateWanted, int bufferSizeWanted) - { - close(); - - firstHostTime = true; - lastNumFrames = 0; - xrun = 0; - lastError.clear(); - - requestedInputChannels = inputChannelsWanted; - requestedOutputChannels = outputChannelsWanted; - targetSampleRate = sampleRateWanted; - targetBufferSize = bufferSizeWanted > 0 ? bufferSizeWanted : defaultBufferSize; - - JUCE_IOS_AUDIO_LOG ("Opening audio device:" - << " inputChannelsWanted: " << requestedInputChannels .toString (2) - << ", outputChannelsWanted: " << requestedOutputChannels.toString (2) - << ", targetSampleRate: " << targetSampleRate - << ", targetBufferSize: " << targetBufferSize); - - setAudioSessionActive (true); - setAudioSessionCategory (requestedInputChannels > 0 ? AVAudioSessionCategoryPlayAndRecord - : AVAudioSessionCategoryPlayback); - channelData.reconfigure (requestedInputChannels, requestedOutputChannels); - updateHardwareInfo (true); - setTargetSampleRateAndBufferSize(); - fixAudioRouteIfSetToReceiver(); - - isRunning = true; - - if (! createAudioUnit()) - { - lastError = "Couldn't open the device"; - return lastError; - } - - const ScopedLock sl (callbackLock); - - AudioOutputUnitStart (audioUnit); - - if (callback != nullptr) - callback->audioDeviceAboutToStart (&owner); - - return lastError; - } - - void close() - { - stop(); - - if (isRunning) - { - isRunning = false; - - if (audioUnit != nullptr) - { - AudioOutputUnitStart (audioUnit); - AudioComponentInstanceDispose (audioUnit); - audioUnit = nullptr; - } - - setAudioSessionActive (false); - } - } - - void start (AudioIODeviceCallback* newCallback) - { - if (isRunning && callback != newCallback) - { - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (&owner); - - const ScopedLock sl (callbackLock); - callback = newCallback; - } - } - - void stop() - { - if (isRunning) - { - AudioIODeviceCallback* lastCallback; - - { - const ScopedLock sl (callbackLock); - lastCallback = callback; - callback = nullptr; - } - - if (lastCallback != nullptr) - lastCallback->audioDeviceStopped(); - } - } - - bool setAudioPreprocessingEnabled (bool enable) - { - auto session = [AVAudioSession sharedInstance]; - - NSString* mode = (enable ? AVAudioSessionModeDefault - : AVAudioSessionModeMeasurement); - - JUCE_NSERROR_CHECK ([session setMode: mode - error: &error]); - - return session.mode == mode; - } - - //============================================================================== - bool canControlTransport() override { return interAppAudioConnected; } - - void transportPlay (bool shouldSartPlaying) override - { - if (! canControlTransport()) - return; - - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); - - Boolean hostIsPlaying = NO; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - &hostIsPlaying, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - - ignoreUnused (err); - jassert (err == noErr); - - if (hostIsPlaying != shouldSartPlaying) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause); - } - - void transportRecord (bool shouldStartRecording) override - { - if (! canControlTransport()) - return; - - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); - - Boolean hostIsRecording = NO; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - nullptr, - &hostIsRecording, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - ignoreUnused (err); - jassert (err == noErr); - - if (hostIsRecording != shouldStartRecording) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord); - } - - void transportRewind() override - { - if (canControlTransport()) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind); - } - - bool getCurrentPosition (CurrentPositionInfo& result) override - { - if (! canControlTransport()) - return false; - - zerostruct (result); - - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); - - if (callbackInfo.hostUserData == nullptr) - return false; - - Boolean hostIsPlaying = NO; - Boolean hostIsRecording = NO; - Float64 hostCurrentSampleInTimeLine = 0; - Boolean hostIsCycling = NO; - Float64 hostCycleStartBeat = 0; - Float64 hostCycleEndBeat = 0; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - &hostIsPlaying, - &hostIsRecording, - nullptr, - &hostCurrentSampleInTimeLine, - &hostIsCycling, - &hostCycleStartBeat, - &hostCycleEndBeat); - if (err == kAUGraphErr_CannotDoInCurrentContext) - return false; - - jassert (err == noErr); - - result.timeInSamples = (int64) hostCurrentSampleInTimeLine; - result.isPlaying = hostIsPlaying; - result.isRecording = hostIsRecording; - result.isLooping = hostIsCycling; - result.ppqLoopStart = hostCycleStartBeat; - result.ppqLoopEnd = hostCycleEndBeat; - - result.timeInSeconds = result.timeInSamples / sampleRate; - - Float64 hostBeat = 0; - Float64 hostTempo = 0; - err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData, - &hostBeat, - &hostTempo); - jassert (err == noErr); - - result.ppqPosition = hostBeat; - result.bpm = hostTempo; - - Float32 hostTimeSigNumerator = 0; - UInt32 hostTimeSigDenominator = 0; - Float64 hostCurrentMeasureDownBeat = 0; - err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData, - nullptr, - &hostTimeSigNumerator, - &hostTimeSigDenominator, - &hostCurrentMeasureDownBeat); - jassert (err == noErr); - - result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat; - result.timeSigNumerator = (int) hostTimeSigNumerator; - result.timeSigDenominator = (int) hostTimeSigDenominator; - - result.frameRate = AudioPlayHead::fpsUnknown; - - return true; - } - - //============================================================================== - #if JUCE_MODULE_AVAILABLE_juce_graphics - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - Image getIcon (int size) - { - if (interAppAudioConnected) - { - UIImage* hostUIImage = AudioOutputUnitGetHostIcon (audioUnit, size); - if (hostUIImage != nullptr) - return juce_createImageFromUIImage (hostUIImage); - } - return Image(); - } - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - #endif - - void switchApplication() - { - if (! interAppAudioConnected) - return; - - CFURLRef hostUrl; - UInt32 dataSize = sizeof (hostUrl); - OSStatus err = AudioUnitGetProperty(audioUnit, - kAudioUnitProperty_PeerURL, - kAudioUnitScope_Global, - 0, - &hostUrl, - &dataSize); - if (err == noErr) - { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - if (@available (iOS 10.0, *)) - { - [[UIApplication sharedApplication] openURL: (NSURL*) hostUrl - options: @{} - completionHandler: nil]; - - return; - } - #endif - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - [[UIApplication sharedApplication] openURL: (NSURL*) hostUrl]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - } - - //============================================================================== - void invokeAudioDeviceErrorCallback (const String& reason) - { - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - callback->audioDeviceError (reason); - } - - void handleStatusChange (bool enabled, const char* reason) - { - const ScopedLock myScopedLock (callbackLock); - - JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); - - isRunning = enabled; - setAudioSessionActive (enabled); - - if (enabled) - AudioOutputUnitStart (audioUnit); - else - AudioOutputUnitStop (audioUnit); - - if (! enabled) - invokeAudioDeviceErrorCallback (reason); - } - - void handleRouteChange (AVAudioSessionRouteChangeReason reason) - { - const ScopedLock myScopedLock (callbackLock); - - const String reasonString (getRoutingChangeReason (reason)); - JUCE_IOS_AUDIO_LOG ("handleRouteChange: " << reasonString); - - if (isRunning) - invokeAudioDeviceErrorCallback (reasonString); - - switch (reason) - { - case AVAudioSessionRouteChangeReasonCategoryChange: - case AVAudioSessionRouteChangeReasonOverride: - case AVAudioSessionRouteChangeReasonRouteConfigurationChange: - break; - case AVAudioSessionRouteChangeReasonUnknown: - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: - case AVAudioSessionRouteChangeReasonWakeFromSleep: - case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: - { - hardwareInfoNeedsUpdating = true; - triggerAsyncUpdate(); - break; - } - - // No default so the code doesn't compile if this enum is extended. - } - } - - void handleAudioUnitPropertyChange (AudioUnit, - AudioUnitPropertyID propertyID, - AudioUnitScope scope, - AudioUnitElement element) - { - ignoreUnused (scope); - ignoreUnused (element); - JUCE_IOS_AUDIO_LOG ("handleAudioUnitPropertyChange: propertyID: " << String (propertyID) - << " scope: " << String (scope) - << " element: " << String (element)); - - switch (propertyID) - { - case kAudioUnitProperty_IsInterAppConnected: - handleInterAppAudioConnectionChange(); - return; - case kAudioUnitProperty_StreamFormat: - handleStreamFormatChange(); - return; - default: - jassertfalse; - } - } - - void handleInterAppAudioConnectionChange() - { - UInt32 connected; - UInt32 dataSize = sizeof (connected); - OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_IsInterAppConnected, - kAudioUnitScope_Global, 0, &connected, &dataSize); - ignoreUnused (err); - jassert (err == noErr); - - JUCE_IOS_AUDIO_LOG ("handleInterAppAudioConnectionChange: " << (connected ? "connected" - : "disconnected")); - - if (connected != interAppAudioConnected) - { - const ScopedLock myScopedLock (callbackLock); - - interAppAudioConnected = connected; - - UIApplicationState appstate = [UIApplication sharedApplication].applicationState; - bool inForeground = (appstate != UIApplicationStateBackground); - - if (interAppAudioConnected || inForeground) - { - setAudioSessionActive (true); - AudioOutputUnitStart (audioUnit); - - if (callback != nullptr) - callback->audioDeviceAboutToStart (&owner); - } - else if (! inForeground) - { - AudioOutputUnitStop (audioUnit); - setAudioSessionActive (false); - - if (callback != nullptr) - callback->audioDeviceStopped(); - } - } - } - - //============================================================================== - OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, - const UInt32 numFrames, AudioBufferList* data) - { - OSStatus err = noErr; - - recordXruns (time, numFrames); - - const bool useInput = channelData.areInputChannelsAvailable(); - - if (useInput) - err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); - - const auto channelDataSize = sizeof (float) * numFrames; - - const ScopedTryLock stl (callbackLock); - - if (stl.isLocked() && callback != nullptr) - { - if ((int) numFrames > channelData.getFloatBufferSize()) - channelData.setFloatBufferSize ((int) numFrames); - - float** const inputData = channelData.audioData.getArrayOfWritePointers(); - float** const outputData = inputData + channelData.inputs->numActiveChannels; - - if (useInput) - { - for (int c = 0; c < channelData.inputs->numActiveChannels; ++c) - { - auto channelIndex = channelData.inputs->activeChannelIndices[c]; - memcpy (inputData[c], (float*) data->mBuffers[channelIndex].mData, channelDataSize); - } - } - else - { - for (int c = 0; c < channelData.inputs->numActiveChannels; ++c) - zeromem (inputData[c], channelDataSize); - } - - const auto nanos = time != nullptr ? timeConversions.hostTimeToNanos (time->mHostTime) : 0; - - callback->audioDeviceIOCallbackWithContext ((const float**) inputData, - channelData.inputs ->numActiveChannels, - outputData, - channelData.outputs->numActiveChannels, - (int) numFrames, - { (time != nullptr && (time->mFlags & kAudioTimeStampHostTimeValid) != 0) ? &nanos : nullptr }); - - for (int c = 0; c < channelData.outputs->numActiveChannels; ++c) - { - auto channelIndex = channelData.outputs->activeChannelIndices[c]; - memcpy (data->mBuffers[channelIndex].mData, outputData[c], channelDataSize); - } - - for (auto c : channelData.outputs->inactiveChannelIndices) - zeromem (data->mBuffers[c].mData, channelDataSize); - } - else - { - for (uint32 c = 0; c < data->mNumberBuffers; ++c) - zeromem (data->mBuffers[c].mData, channelDataSize); - } - - return err; - } - - void recordXruns (const AudioTimeStamp* time, UInt32 numFrames) - { - if (time != nullptr && (time->mFlags & kAudioTimeStampSampleTimeValid) != 0) - { - if (! firstHostTime) - { - if ((time->mSampleTime - lastSampleTime) != lastNumFrames) - xrun++; - } - else - firstHostTime = false; - - lastSampleTime = time->mSampleTime; - } - else - firstHostTime = true; - - lastNumFrames = numFrames; - } - - //============================================================================== - static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, - UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) - { - return static_cast (client)->process (flags, time, numFrames, data); - } - - //============================================================================== - bool createAudioUnit() - { - JUCE_IOS_AUDIO_LOG ("Creating the audio unit"); - - if (audioUnit != nullptr) - { - AudioComponentInstanceDispose (audioUnit); - audioUnit = nullptr; - } - - AudioComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_RemoteIO; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - AudioComponent comp = AudioComponentFindNext (nullptr, &desc); - AudioComponentInstanceNew (comp, &audioUnit); - - if (audioUnit == nullptr) - return false; - - #if JucePlugin_Enable_IAA - AudioComponentDescription appDesc; - appDesc.componentType = JucePlugin_IAAType; - appDesc.componentSubType = JucePlugin_IAASubType; - appDesc.componentManufacturer = JucePlugin_ManufacturerCode; - appDesc.componentFlags = 0; - appDesc.componentFlagsMask = 0; - OSStatus err = AudioOutputUnitPublish (&appDesc, - CFSTR(JucePlugin_IAAName), - JucePlugin_VersionCode, - audioUnit); - - // This assert will be hit if the Inter-App Audio entitlement has not - // been enabled, or the description being published with - // AudioOutputUnitPublish is different from any in the AudioComponents - // array in this application's .plist file. - jassert (err == noErr); - - err = AudioUnitAddPropertyListener (audioUnit, - kAudioUnitProperty_IsInterAppConnected, - dispatchAudioUnitPropertyChange, - this); - jassert (err == noErr); - - AudioOutputUnitMIDICallbacks midiCallbacks; - midiCallbacks.userData = this; - midiCallbacks.MIDIEventProc = midiEventCallback; - midiCallbacks.MIDISysExProc = midiSysExCallback; - err = AudioUnitSetProperty (audioUnit, - kAudioOutputUnitProperty_MIDICallbacks, - kAudioUnitScope_Global, - 0, - &midiCallbacks, - sizeof (midiCallbacks)); - jassert (err == noErr); - #endif - - if (channelData.areInputChannelsAvailable()) - { - const UInt32 one = 1; - AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one)); - } - - { - AURenderCallbackStruct inputProc; - inputProc.inputProc = processStatic; - inputProc.inputProcRefCon = this; - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc)); - } - - { - AudioStreamBasicDescription format; - zerostruct (format); - format.mSampleRate = sampleRate; - format.mFormatID = kAudioFormatLinearPCM; - format.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved | kAudioFormatFlagsNativeEndian | kLinearPCMFormatFlagIsPacked; - format.mBitsPerChannel = 8 * sizeof (float); - format.mFramesPerPacket = 1; - format.mChannelsPerFrame = (UInt32) jmax (channelData.inputs->numHardwareChannels, channelData.outputs->numHardwareChannels); - format.mBytesPerFrame = format.mBytesPerPacket = sizeof (float); - - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); - } - - AudioUnitInitialize (audioUnit); - - { - // Querying the kAudioUnitProperty_MaximumFramesPerSlice property after calling AudioUnitInitialize - // seems to be more reliable than calling it before. - UInt32 framesPerSlice, dataSize = sizeof (framesPerSlice); - - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr - && dataSize == sizeof (framesPerSlice) - && static_cast (framesPerSlice) != bufferSize) - { - JUCE_IOS_AUDIO_LOG ("Internal buffer size: " << String (framesPerSlice)); - channelData.setFloatBufferSize (static_cast (framesPerSlice)); - } - } - - AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, dispatchAudioUnitPropertyChange, this); - - return true; - } - - void fillHostCallbackInfo (HostCallbackInfo& callbackInfo) - { - zerostruct (callbackInfo); - UInt32 dataSize = sizeof (HostCallbackInfo); - OSStatus err = AudioUnitGetProperty (audioUnit, - kAudioUnitProperty_HostCallbacks, - kAudioUnitScope_Global, - 0, - &callbackInfo, - &dataSize); - ignoreUnused (err); - jassert (err == noErr); - } - - void handleAudioTransportEvent (AudioUnitRemoteControlEvent event) - { - OSStatus err = AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_RemoteControlToHost, - kAudioUnitScope_Global, 0, &event, sizeof (event)); - ignoreUnused (err); - jassert (err == noErr); - } - - // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it - // to make it loud. Needed because by default when using an input + output, the output is kept quiet. - static void fixAudioRouteIfSetToReceiver() - { - auto session = [AVAudioSession sharedInstance]; - auto route = session.currentRoute; - - for (AVAudioSessionPortDescription* port in route.outputs) - { - if ([port.portName isEqualToString: @"Receiver"]) - { - JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker - error: &error]); - setAudioSessionActive (true); - } - } - } - - void restart() - { - const ScopedLock sl (callbackLock); - - updateHardwareInfo(); - setTargetSampleRateAndBufferSize(); - - if (isRunning) - { - if (audioUnit != nullptr) - { - AudioComponentInstanceDispose (audioUnit); - audioUnit = nullptr; - - if (callback != nullptr) - callback->audioDeviceStopped(); - } - - channelData.reconfigure (requestedInputChannels, requestedOutputChannels); - - createAudioUnit(); - - if (audioUnit != nullptr) - { - isRunning = true; - - if (callback != nullptr) - callback->audioDeviceAboutToStart (&owner); - - AudioOutputUnitStart (audioUnit); - } - } - } - - void handleAsyncUpdate() override - { - restart(); - } - - void handleStreamFormatChange() - { - AudioStreamBasicDescription desc; - zerostruct (desc); - UInt32 dataSize = sizeof (desc); - AudioUnitGetProperty (audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, - 0, - &desc, - &dataSize); - - if (desc.mSampleRate != 0 && desc.mSampleRate != sampleRate) - { - JUCE_IOS_AUDIO_LOG ("Stream format has changed: Sample rate " << desc.mSampleRate); - triggerAsyncUpdate(); - } - } - - static void dispatchAudioUnitPropertyChange (void* data, AudioUnit unit, AudioUnitPropertyID propertyID, - AudioUnitScope scope, AudioUnitElement element) - { - static_cast (data)->handleAudioUnitPropertyChange (unit, propertyID, scope, element); - } - - static double getTimestampForMIDI() - { - return Time::getMillisecondCounter() / 1000.0; - } - - static void midiEventCallback (void *client, UInt32 status, UInt32 data1, UInt32 data2, UInt32) - { - return static_cast (client)->handleMidiMessage (MidiMessage ((int) status, - (int) data1, - (int) data2, - getTimestampForMIDI())); - } - - static void midiSysExCallback (void *client, const UInt8 *data, UInt32 length) - { - return static_cast (client)->handleMidiMessage (MidiMessage (data, (int) length, getTimestampForMIDI())); - } - - void handleMidiMessage (MidiMessage msg) - { - if (messageCollector != nullptr) - messageCollector->addMessageToQueue (msg); - } - - struct IOChannelData - { - class IOChannelConfig - { - public: - IOChannelConfig (const bool isInput, const BigInteger requiredChannels) - : hardwareChannelNames (getHardwareChannelNames (isInput)), - numHardwareChannels (hardwareChannelNames.size()), - areChannelsAccessible ((! isInput) || [AVAudioSession sharedInstance].isInputAvailable), - activeChannels (limitRequiredChannelsToHardware (numHardwareChannels, requiredChannels)), - numActiveChannels (activeChannels.countNumberOfSetBits()), - activeChannelIndices (getActiveChannelIndices (activeChannels)), - inactiveChannelIndices (getInactiveChannelIndices (activeChannelIndices, numHardwareChannels)) - { - #if JUCE_IOS_AUDIO_LOGGING - { - String info; - - info << "Number of hardware channels: " << numHardwareChannels - << ", Hardware channel names:"; - - for (auto& name : hardwareChannelNames) - info << " \"" << name << "\""; - - info << ", Are channels available: " << (areChannelsAccessible ? "yes" : "no") - << ", Active channel indices:"; - - for (auto i : activeChannelIndices) - info << " " << i; - - info << ", Inactive channel indices:"; - - for (auto i : inactiveChannelIndices) - info << " " << i; - - JUCE_IOS_AUDIO_LOG ((isInput ? "Input" : "Output") << " channel configuration: {" << info << "}"); - } - #endif - } - - const StringArray hardwareChannelNames; - const int numHardwareChannels; - const bool areChannelsAccessible; - - const BigInteger activeChannels; - const int numActiveChannels; - - const Array activeChannelIndices, inactiveChannelIndices; - - private: - static StringArray getHardwareChannelNames (const bool isInput) - { - StringArray result; - - auto route = [AVAudioSession sharedInstance].currentRoute; - - for (AVAudioSessionPortDescription* port in (isInput ? route.inputs : route.outputs)) - { - for (AVAudioSessionChannelDescription* desc in port.channels) - result.add (nsStringToJuce (desc.channelName)); - } - - // A fallback for the iOS simulator and older iOS versions - if (result.isEmpty()) - return { "Left", "Right" }; - - return result; - } - - static BigInteger limitRequiredChannelsToHardware (const int numHardwareChannelsAvailable, - BigInteger requiredChannels) - { - requiredChannels.setRange (numHardwareChannelsAvailable, - requiredChannels.getHighestBit() + 1, - false); - - return requiredChannels; - } - - static Array getActiveChannelIndices (const BigInteger activeChannelsToIndex) - { - Array result; - - auto index = activeChannelsToIndex.findNextSetBit (0); - - while (index != -1) - { - result.add (index); - index = activeChannelsToIndex.findNextSetBit (++index); - } - - return result; - } - - static Array getInactiveChannelIndices (const Array& activeIndices, int numChannels) - { - Array result; - - auto nextActiveChannel = activeIndices.begin(); - - for (int i = 0; i < numChannels; ++i) - if (nextActiveChannel != activeIndices.end() && i == *nextActiveChannel) - ++nextActiveChannel; - else - result.add (i); - - return result; - } - }; - - void reconfigure (const BigInteger requiredInputChannels, - const BigInteger requiredOutputChannels) - { - inputs .reset (new IOChannelConfig (true, requiredInputChannels)); - outputs.reset (new IOChannelConfig (false, requiredOutputChannels)); - - audioData.setSize (inputs->numActiveChannels + outputs->numActiveChannels, - audioData.getNumSamples()); - } - - int getFloatBufferSize() const - { - return audioData.getNumSamples(); - } - - void setFloatBufferSize (const int newSize) - { - audioData.setSize (audioData.getNumChannels(), newSize); - } - - bool areInputChannelsAvailable() const - { - return inputs->areChannelsAccessible && inputs->numActiveChannels > 0; - } - - std::unique_ptr inputs; - std::unique_ptr outputs; - - AudioBuffer audioData { 0, 0 }; - }; - - CoreAudioTimeConversions timeConversions; - - IOChannelData channelData; - - BigInteger requestedInputChannels, requestedOutputChannels; - - bool isRunning = false; - - AudioIODeviceCallback* callback = nullptr; - - String lastError; - - #if TARGET_IPHONE_SIMULATOR - static constexpr int defaultBufferSize = 512; - #else - static constexpr int defaultBufferSize = 256; - #endif - int targetBufferSize = defaultBufferSize, bufferSize = targetBufferSize; - - double targetSampleRate = 44100.0, sampleRate = targetSampleRate; - - Array availableSampleRates; - Array availableBufferSizes; - - bool interAppAudioConnected = false; - - MidiMessageCollector* messageCollector = nullptr; - - WeakReference deviceType; - iOSAudioIODevice& owner; - - CriticalSection callbackLock; - - Atomic hardwareInfoNeedsUpdating { true }; - - AudioUnit audioUnit {}; - - SharedResourcePointer sessionHolder; - - bool firstHostTime; - Float64 lastSampleTime; - unsigned int lastNumFrames; - int xrun; - - JUCE_DECLARE_NON_COPYABLE (Pimpl) -}; - -//============================================================================== -iOSAudioIODevice::iOSAudioIODevice (iOSAudioIODeviceType* ioDeviceType, const String&, const String&) - : AudioIODevice (iOSAudioDeviceName, iOSAudioDeviceName), - pimpl (new Pimpl (ioDeviceType, *this)) -{ -} - -//============================================================================== -String iOSAudioIODevice::open (const BigInteger& inChans, const BigInteger& outChans, - double requestedSampleRate, int requestedBufferSize) -{ - return pimpl->open (inChans, outChans, requestedSampleRate, requestedBufferSize); -} - -void iOSAudioIODevice::close() { pimpl->close(); } - -void iOSAudioIODevice::start (AudioIODeviceCallback* callbackToUse) { pimpl->start (callbackToUse); } -void iOSAudioIODevice::stop() { pimpl->stop(); } - -Array iOSAudioIODevice::getAvailableSampleRates() { return pimpl->availableSampleRates; } -Array iOSAudioIODevice::getAvailableBufferSizes() { return pimpl->availableBufferSizes; } - -bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enabled) { return pimpl->setAudioPreprocessingEnabled (enabled); } - -bool iOSAudioIODevice::isPlaying() { return pimpl->isRunning && pimpl->callback != nullptr; } -bool iOSAudioIODevice::isOpen() { return pimpl->isRunning; } -String iOSAudioIODevice::getLastError() { return pimpl->lastError; } - -StringArray iOSAudioIODevice::getOutputChannelNames() { return pimpl->channelData.outputs->hardwareChannelNames; } -StringArray iOSAudioIODevice::getInputChannelNames() { return pimpl->channelData.inputs->areChannelsAccessible ? pimpl->channelData.inputs->hardwareChannelNames : StringArray(); } - -int iOSAudioIODevice::getDefaultBufferSize() { return pimpl->defaultBufferSize; } -int iOSAudioIODevice::getCurrentBufferSizeSamples() { return pimpl->bufferSize; } - -double iOSAudioIODevice::getCurrentSampleRate() { return pimpl->sampleRate; } - -int iOSAudioIODevice::getCurrentBitDepth() { return 16; } - -BigInteger iOSAudioIODevice::getActiveInputChannels() const { return pimpl->channelData.inputs->activeChannels; } -BigInteger iOSAudioIODevice::getActiveOutputChannels() const { return pimpl->channelData.outputs->activeChannels; } - -int iOSAudioIODevice::getInputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].inputLatency); } -int iOSAudioIODevice::getOutputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].outputLatency); } -int iOSAudioIODevice::getXRunCount() const noexcept { return pimpl->xrun; } - -void iOSAudioIODevice::setMidiMessageCollector (MidiMessageCollector* collector) { pimpl->messageCollector = collector; } -AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl.get(); } - -bool iOSAudioIODevice::isInterAppAudioConnected() const { return pimpl->interAppAudioConnected; } -#if JUCE_MODULE_AVAILABLE_juce_graphics -Image iOSAudioIODevice::getIcon (int size) { return pimpl->getIcon (size); } -#endif -void iOSAudioIODevice::switchApplication() { return pimpl->switchApplication(); } - -//============================================================================== -iOSAudioIODeviceType::iOSAudioIODeviceType() - : AudioIODeviceType (iOSAudioDeviceName) -{ - sessionHolder->activeDeviceTypes.add (this); -} - -iOSAudioIODeviceType::~iOSAudioIODeviceType() -{ - sessionHolder->activeDeviceTypes.removeFirstMatchingValue (this); -} - -// The list of devices is updated automatically -void iOSAudioIODeviceType::scanForDevices() {} -StringArray iOSAudioIODeviceType::getDeviceNames (bool) const { return { iOSAudioDeviceName }; } -int iOSAudioIODeviceType::getDefaultDeviceIndex (bool) const { return 0; } -int iOSAudioIODeviceType::getIndexOfDevice (AudioIODevice*, bool) const { return 0; } -bool iOSAudioIODeviceType::hasSeparateInputsAndOutputs() const { return false; } - -AudioIODevice* iOSAudioIODeviceType::createDevice (const String& outputDeviceName, const String& inputDeviceName) -{ - return new iOSAudioIODevice (this, outputDeviceName, inputDeviceName); -} - -void iOSAudioIODeviceType::handleRouteChange (AVAudioSessionRouteChangeReason) -{ - triggerAsyncUpdate(); -} - -void iOSAudioIODeviceType::handleAsyncUpdate() -{ - callDeviceChangeListeners(); -} - -//============================================================================== -AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } -AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } - -void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const -{ - for (auto device: activeDevices) - device->handleStatusChange (enabled, reason); -} - -void AudioSessionHolder::handleRouteChange (AVAudioSessionRouteChangeReason reason) -{ - for (auto device: activeDevices) - device->handleRouteChange (reason); - - for (auto deviceType: activeDeviceTypes) - deviceType->handleRouteChange (reason); -} - -#undef JUCE_NSERROR_CHECK - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_ios_Audio.h b/source/modules/juce_audio_devices/native/juce_ios_Audio.h deleted file mode 100644 index 8728f66e7..000000000 --- a/source/modules/juce_audio_devices/native/juce_ios_Audio.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -class iOSAudioIODeviceType; - -class iOSAudioIODevice : public AudioIODevice -{ -public: - //============================================================================== - String open (const BigInteger&, const BigInteger&, double, int) override; - void close() override; - - void start (AudioIODeviceCallback*) override; - void stop() override; - - Array getAvailableSampleRates() override; - Array getAvailableBufferSizes() override; - - bool setAudioPreprocessingEnabled (bool) override; - - //============================================================================== - bool isPlaying() override; - bool isOpen() override; - String getLastError() override; - - //============================================================================== - StringArray getOutputChannelNames() override; - StringArray getInputChannelNames() override; - - int getDefaultBufferSize() override; - int getCurrentBufferSizeSamples() override; - - double getCurrentSampleRate() override; - - int getCurrentBitDepth() override; - - BigInteger getActiveOutputChannels() const override; - BigInteger getActiveInputChannels() const override; - - int getOutputLatencyInSamples() override; - int getInputLatencyInSamples() override; - - int getXRunCount() const noexcept override; - - //============================================================================== - void setMidiMessageCollector (MidiMessageCollector*); - AudioPlayHead* getAudioPlayHead() const; - - //============================================================================== - bool isInterAppAudioConnected() const; - #if JUCE_MODULE_AVAILABLE_juce_graphics - Image getIcon (int size); - #endif - void switchApplication(); - -private: - //============================================================================== - iOSAudioIODevice (iOSAudioIODeviceType*, const String&, const String&); - - //============================================================================== - friend class iOSAudioIODeviceType; - friend struct AudioSessionHolder; - - struct Pimpl; - std::unique_ptr pimpl; - - JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_processors/format_types/LV2_SDK/generate_lv2_bundle_sources.py b/source/modules/juce_audio_processors/format_types/LV2_SDK/generate_lv2_bundle_sources.py index 5b74f0a83..299361a4a 100644 --- a/source/modules/juce_audio_processors/format_types/LV2_SDK/generate_lv2_bundle_sources.py +++ b/source/modules/juce_audio_processors/format_types/LV2_SDK/generate_lv2_bundle_sources.py @@ -52,7 +52,7 @@ FUNCTION_TEMPLATE = """/* ============================================================================== This file is part of the JUCE library. - Copyright (c) 2020 - Raw Material Software Limited + Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. @@ -79,6 +79,8 @@ FUNCTION_TEMPLATE = """/* #pragma once +#ifndef DOXYGEN + #include namespace juce @@ -109,7 +111,8 @@ std::vector juce::lv2::Bundle::getAllBundles() {} }}; }} -""" + +#endif""" def chunks(lst, n): @@ -149,5 +152,6 @@ args = parser.parse_args() print(FUNCTION_TEMPLATE.format(", ".join(generate_bundle_source(root, files) for root, files in filter_ttl_files(args.lv2_dir) - if len(files) != 0)), + if len(files) != 0)) + .replace("\t", " "), end = "\r\n") diff --git a/source/modules/juce_audio_processors/format_types/juce_ARAHosting.h b/source/modules/juce_audio_processors/format_types/juce_ARAHosting.h index 40203d561..632cb824b 100644 --- a/source/modules/juce_audio_processors/format_types/juce_ARAHosting.h +++ b/source/modules/juce_audio_processors/format_types/juce_ARAHosting.h @@ -102,17 +102,26 @@ struct ConversionFunctions }; //============================================================================== +/** This class is used by the various ARA model object helper classes, such as MusicalContext, + AudioSource etc. It helps with deregistering the model objects from the DocumentController + when the lifetime of the helper class object ends. + + You shouldn't use this class directly but instead inherit from the helper classes. +*/ template class ManagedARAHandle { public: using Ptr = PtrIn; + /** Constructor. */ ManagedARAHandle (ARA::Host::DocumentController& dc, Ptr ptr) noexcept : handle (ptr, Deleter { dc }) {} + /** Returns the host side DocumentController reference. */ auto& getDocumentController() const { return handle.get_deleter().documentController; } + /** Returns the plugin side reference to the model object. */ Ptr getPluginRef() const { return handle.get(); } private: @@ -268,12 +277,34 @@ private: AudioSource& source; }; +/** This class is used internally by PlaybackRegionRegistry to be notified when a PlaybackRegion + object is deleted. +*/ struct DeletionListener { + /** Destructor. */ virtual ~DeletionListener() = default; + + /** Removes another DeletionListener object from this DeletionListener. */ virtual void removeListener (DeletionListener& other) noexcept = 0; }; +/** Helper class for the host side implementation of the %ARA %PlaybackRegion model object. + + Its intended use is to add a member variable of this type to your host side %PlaybackRegion + implementation. Then it provides a RAII approach to managing the lifetime of the corresponding + objects created inside the DocumentController. When the host side object is instantiated an ARA + model object is also created in the DocumentController. When the host side object is deleted it + will be removed from the DocumentController as well. + + The class will automatically put the DocumentController into editable state for operations that + mandate this e.g. creation, deletion or updating. + + You can encapsulate multiple such operations into a scope with an ARAEditGuard in order to invoke + the editable state of the DocumentController only once. + + @tags{ARA} +*/ struct PlaybackRegion { public: diff --git a/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h b/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h index de0b7650c..5ef274d03 100644 --- a/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h +++ b/source/modules/juce_audio_processors/format_types/juce_AU_Shared.h @@ -23,6 +23,8 @@ ============================================================================== */ +#ifndef DOXYGEN + // This macro can be set if you need to override this internal name for some reason.. #ifndef JUCE_STATE_DICTIONARY_KEY #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" @@ -31,8 +33,6 @@ namespace juce { -#ifndef DOXYGEN - struct AudioUnitHelpers { class ChannelRemapper @@ -560,6 +560,6 @@ struct AudioUnitHelpers } }; -#endif - } // namespace juce + +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm index c40fcfd45..ec695c4f8 100644 --- a/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm +++ b/source/modules/juce_audio_processors/format_types/juce_AudioUnitPluginFormat.mm @@ -1385,7 +1385,10 @@ public: void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, bool processBlockBypassedCalled) { - if (const auto* hostTimeNs = getHostTimeNs()) + auto* playhead = getPlayHead(); + const auto position = playhead != nullptr ? playhead->getPosition() : nullopt; + + if (const auto hostTimeNs = position.hasValue() ? position->getHostTimeNs() : nullopt) { timeStamp.mHostTime = *hostTimeNs; timeStamp.mFlags |= kAudioTimeStampHostTimeValid; @@ -2298,12 +2301,10 @@ private: { if (auto* ph = getPlayHead()) { - AudioPlayHead::CurrentPositionInfo result; - - if (ph->getCurrentPosition (result)) + if (const auto pos = ph->getPosition()) { - setIfNotNull (outCurrentBeat, result.ppqPosition); - setIfNotNull (outCurrentTempo, result.bpm); + setIfNotNull (outCurrentBeat, pos->getPpqPosition().orFallback (0.0)); + setIfNotNull (outCurrentTempo, pos->getBpm().orFallback (0.0)); return noErr; } } @@ -2318,14 +2319,13 @@ private: { if (auto* ph = getPlayHead()) { - AudioPlayHead::CurrentPositionInfo result; - - if (ph->getCurrentPosition (result)) + if (const auto pos = ph->getPosition()) { + const auto signature = pos->getTimeSignature().orFallback (AudioPlayHead::TimeSignature{}); setIfNotNull (outDeltaSampleOffsetToNextBeat, (UInt32) 0); //xxx - setIfNotNull (outTimeSig_Numerator, (UInt32) result.timeSigNumerator); - setIfNotNull (outTimeSig_Denominator, (UInt32) result.timeSigDenominator); - setIfNotNull (outCurrentMeasureDownBeat, result.ppqPositionOfLastBarStart); //xxx wrong + setIfNotNull (outTimeSig_Numerator, (UInt32) signature.numerator); + setIfNotNull (outTimeSig_Denominator, (UInt32) signature.denominator); + setIfNotNull (outCurrentMeasureDownBeat, pos->getPpqPositionOfLastBarStart().orFallback (0.0)); //xxx wrong return noErr; } } diff --git a/source/modules/juce_audio_processors/format_types/juce_LV2Common.h b/source/modules/juce_audio_processors/format_types/juce_LV2Common.h index 325f0aad0..d3f3275db 100644 --- a/source/modules/juce_audio_processors/format_types/juce_LV2Common.h +++ b/source/modules/juce_audio_processors/format_types/juce_LV2Common.h @@ -25,8 +25,9 @@ #pragma once +#ifndef DOXYGEN + #include "juce_lv2_config.h" -#include "juce_core/containers/juce_Optional.h" #ifdef Bool #undef Bool // previously defined in X11/Xlib.h @@ -135,16 +136,6 @@ struct ObjectTraits { static constexpr auto construct = lv2_atom_forge_object; using SequenceFrame = ScopedFrame; using ObjectFrame = ScopedFrame; -template -bool withValue (const Optional& opt, Callback&& callback) -{ - if (! opt.hasValue()) - return false; - - callback (*opt); - return true; -} - struct NumericAtomParser { explicit NumericAtomParser (LV2_URID_Map mapFeatureIn) @@ -626,3 +617,5 @@ static inline std::vector findStableBusOrder (const String& mainGro } } + +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp index 6f2dfd4ae..53419384f 100644 --- a/source/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_LV2PluginFormat.cpp @@ -658,7 +658,7 @@ public: const auto index = strings.size(); indices.insert (it, index); - strings.emplace_back (uri); + strings.push_back (uriString); return static_cast (index + 1); } @@ -724,6 +724,7 @@ struct UsefulUrids X (LV2_TIME__beatsPerMinute) X (LV2_TIME__frame) X (LV2_TIME__speed) + X (LV2_TIME__bar) X (LV2_UI__floatProtocol) X (LV2_UNITS__beat) X (LV2_UNITS__frame) @@ -1696,11 +1697,13 @@ static SingleSizeAlignedStorage grow (SingleSizeAlignedStorage static auto data (This& t) -> decltype (t.data()) @@ -1777,112 +1782,9 @@ private: SingleSizeAlignedStorage<8> contents; lv2_shared::AtomForge forge; LV2_Atom_Forge_Frame frame; + SupportsTime time = SupportsTime::no; }; -template -struct ParseResult -{ - Value value; - bool successful; -}; - -class Ports -{ -public: - static constexpr auto sequenceSize = 8192; - - template - void forEachPort (Callback&& callback) const - { - for (const auto& port : controlPorts) - callback (port.header); - - for (const auto& port : cvPorts) - callback (port.header); - - for (const auto& port : audioPorts) - callback (port.header); - - for (const auto& port : atomPorts) - callback (port.header); - } - - auto getControlPorts() { return makeSimpleSpan (controlPorts); } - auto getControlPorts() const { return makeSimpleSpan (controlPorts); } - auto getCvPorts() { return makeSimpleSpan (cvPorts); } - auto getCvPorts() const { return makeSimpleSpan (cvPorts); } - auto getAudioPorts() { return makeSimpleSpan (audioPorts); } - auto getAudioPorts() const { return makeSimpleSpan (audioPorts); } - auto getAtomPorts() { return makeSimpleSpan (atomPorts); } - auto getAtomPorts() const { return makeSimpleSpan (atomPorts); } - -private: - friend ParseResult getPorts (const UsefulUris& uris, const Plugin& plugin, SymbolMap& symap); - - std::vector controlPorts; - std::vector cvPorts; - std::vector audioPorts; - std::vector atomPorts; -}; - -ParseResult getPorts (const UsefulUris& uris, const Plugin& plugin, SymbolMap& symap) -{ - Ports value; - bool successful = true; - - const auto numPorts = plugin.getNumPorts(); - - for (uint32_t i = 0; i != numPorts; ++i) - { - const auto port = plugin.getPortByIndex (i); - - const PortHeader header { String::fromUTF8 (port.getName().getTyped()), - String::fromUTF8 (port.getSymbol().getTyped()), - i, - port.getDirection (uris) }; - - switch (port.getKind (uris)) - { - case Port::Kind::control: - { - value.controlPorts.push_back ({ header, ParameterInfo::getInfoForPort (uris, port) }); - break; - } - - case Port::Kind::cv: - value.cvPorts.push_back ({ header }); - break; - - case Port::Kind::audio: - { - value.audioPorts.push_back ({ header }); - break; - } - - case Port::Kind::atom: - { - value.atomPorts.push_back ({ header, (size_t) Ports::sequenceSize, symap }); - break; - } - - case Port::Kind::unknown: - successful = false; - break; - } - } - - for (auto& atomPort : value.atomPorts) - { - const auto port = plugin.getPortByIndex (atomPort.header.index); - const auto minSize = port.get (uris.mLV2_RESIZE_PORT__minimumSize.get()); - - if (minSize != nullptr) - atomPort.ensureSizeInBytes ((size_t) lilv_node_as_int (minSize.get())); - } - - return { std::move (value), successful }; -} - class Plugins { public: @@ -1981,6 +1883,106 @@ private: std::unique_ptr world; }; +class Ports +{ +public: + static constexpr auto sequenceSize = 8192; + + template + void forEachPort (Callback&& callback) const + { + for (const auto& port : controlPorts) + callback (port.header); + + for (const auto& port : cvPorts) + callback (port.header); + + for (const auto& port : audioPorts) + callback (port.header); + + for (const auto& port : atomPorts) + callback (port.header); + } + + auto getControlPorts() { return makeSimpleSpan (controlPorts); } + auto getControlPorts() const { return makeSimpleSpan (controlPorts); } + auto getCvPorts() { return makeSimpleSpan (cvPorts); } + auto getCvPorts() const { return makeSimpleSpan (cvPorts); } + auto getAudioPorts() { return makeSimpleSpan (audioPorts); } + auto getAudioPorts() const { return makeSimpleSpan (audioPorts); } + auto getAtomPorts() { return makeSimpleSpan (atomPorts); } + auto getAtomPorts() const { return makeSimpleSpan (atomPorts); } + + static Optional getPorts (World& world, const UsefulUris& uris, const Plugin& plugin, SymbolMap& symap) + { + Ports value; + bool successful = true; + + const auto numPorts = plugin.getNumPorts(); + const auto timeNode = world.newUri (LV2_TIME__Position); + + for (uint32_t i = 0; i != numPorts; ++i) + { + const auto port = plugin.getPortByIndex (i); + + const PortHeader header { String::fromUTF8 (port.getName().getTyped()), + String::fromUTF8 (port.getSymbol().getTyped()), + i, + port.getDirection (uris) }; + + switch (port.getKind (uris)) + { + case Port::Kind::control: + { + value.controlPorts.push_back ({ header, ParameterInfo::getInfoForPort (uris, port) }); + break; + } + + case Port::Kind::cv: + value.cvPorts.push_back ({ header }); + break; + + case Port::Kind::audio: + { + value.audioPorts.push_back ({ header }); + break; + } + + case Port::Kind::atom: + { + const auto supportsTime = port.supportsEvent (timeNode.get()); + value.atomPorts.push_back ({ header, + (size_t) Ports::sequenceSize, + symap, + supportsTime ? SupportsTime::yes : SupportsTime::no }); + break; + } + + case Port::Kind::unknown: + successful = false; + break; + } + } + + for (auto& atomPort : value.atomPorts) + { + const auto port = plugin.getPortByIndex (atomPort.header.index); + const auto minSize = port.get (uris.mLV2_RESIZE_PORT__minimumSize.get()); + + if (minSize != nullptr) + atomPort.ensureSizeInBytes ((size_t) lilv_node_as_int (minSize.get())); + } + + return successful ? makeOptional (std::move (value)) : nullopt; + } + +private: + std::vector controlPorts; + std::vector cvPorts; + std::vector audioPorts; + std::vector atomPorts; +}; + class InstanceWithSupports : private FeaturesDataListener, private HandleHolder { @@ -4753,6 +4755,72 @@ private: instance->instance.connectPort (port.header.index, port.data()); } + void writeTimeInfoToPort (AtomPort& port) + { + if (port.header.direction != Port::Direction::input || ! port.getSupportsTime()) + return; + + auto* forge = port.getForge().get(); + auto* playhead = getPlayHead(); + + if (playhead == nullptr) + return; + + // Write timing info to the control port + const auto info = playhead->getPosition(); + + if (! info.hasValue()) + return; + + const auto& urids = instance->urids; + + lv2_atom_forge_frame_time (forge, 0); + + lv2_shared::ObjectFrame object { forge, (uint32_t) 0, urids.mLV2_TIME__Position }; + + lv2_atom_forge_key (forge, urids.mLV2_TIME__speed); + lv2_atom_forge_float (forge, info->getIsPlaying() ? 1.0f : 0.0f); + + if (const auto samples = info->getTimeInSamples()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__frame); + lv2_atom_forge_long (forge, *samples); + } + + if (const auto bar = info->getBarCount()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__bar); + lv2_atom_forge_long (forge, *bar); + } + + if (const auto beat = info->getPpqPosition()) + { + if (const auto barStart = info->getPpqPositionOfLastBarStart()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__barBeat); + lv2_atom_forge_float (forge, (float) (*beat - *barStart)); + } + + lv2_atom_forge_key (forge, urids.mLV2_TIME__beat); + lv2_atom_forge_double (forge, *beat); + } + + if (const auto sig = info->getTimeSignature()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__beatUnit); + lv2_atom_forge_int (forge, sig->denominator); + + lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerBar); + lv2_atom_forge_float (forge, (float) sig->numerator); + } + + if (const auto bpm = info->getBpm()) + { + lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerMinute); + lv2_atom_forge_float (forge, (float) *bpm); + } + } + void preparePortsForRun (AudioBuffer& audio, MidiBuffer& midiBuffer) { connectPorts (audio); @@ -4775,49 +4843,13 @@ private: } } - auto* forge = controlPort != nullptr ? controlPort->getForge().get() - : nullptr; - - if (forge != nullptr) - { - // Write timing info to the control port - if (auto* playhead = getPlayHead()) - { - AudioPlayHead::CurrentPositionInfo info; - - if (playhead->getCurrentPosition (info)) - { - const auto& urids = instance->urids; - - lv2_atom_forge_frame_time (forge, 0); - - lv2_shared::ObjectFrame object { forge, (uint32_t) 0, urids.mLV2_TIME__Position }; - - lv2_atom_forge_key (forge, urids.mLV2_TIME__frame); - lv2_atom_forge_long (forge, info.timeInSamples); - - lv2_atom_forge_key (forge, urids.mLV2_TIME__speed); - lv2_atom_forge_float (forge, info.isPlaying ? 1.0f : 0.0f); - - lv2_atom_forge_key (forge, urids.mLV2_TIME__barBeat); - lv2_atom_forge_float (forge, (float) (info.ppqPosition - info.ppqPositionOfLastBarStart)); - - lv2_atom_forge_key (forge, urids.mLV2_TIME__beat); - lv2_atom_forge_double (forge, info.ppqPosition); - - lv2_atom_forge_key (forge, urids.mLV2_TIME__beatUnit); - lv2_atom_forge_int (forge, info.timeSigDenominator); - - lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerBar); - lv2_atom_forge_float (forge, (float) info.timeSigNumerator); + for (auto& port : instance->ports.getAtomPorts()) + writeTimeInfoToPort (port); - lv2_atom_forge_key (forge, urids.mLV2_TIME__beatsPerMinute); - lv2_atom_forge_float (forge, (float) info.bpm); - } - } - } + const auto controlPortForge = controlPort != nullptr ? controlPort->getForge().get() + : nullptr; - parameterValues.postChangedParametersToProcessor (getParameterWriterUrids(), forge); + parameterValues.postChangedParametersToProcessor (getParameterWriterUrids(), controlPortForge); instance->uiToProcessor.readAllAndClear ([this] (MessageHeader header, uint32_t size, const void* buffer) { @@ -5364,15 +5396,15 @@ public: return lv2_host::PluginState { lilv_state_new_from_world (world->get(), &map, plugin.getUri().get()) }; }(); - auto ports = lv2_host::getPorts (uris, plugin, *symap); + auto ports = lv2_host::Ports::getPorts (*world, uris, plugin, *symap); - if (! ports.successful) + if (! ports.hasValue()) return callback (nullptr, "Plugin has ports of an unsupported type"); auto instance = std::make_unique (*world, std::move (symap), plugin, - std::move (ports.value), + std::move (*ports), (int32_t) initialBufferSize, initialSampleRate); diff --git a/source/modules/juce_audio_processors/format_types/juce_LV2Resources.h b/source/modules/juce_audio_processors/format_types/juce_LV2Resources.h index 1404f6aa5..0ff36da91 100644 --- a/source/modules/juce_audio_processors/format_types/juce_LV2Resources.h +++ b/source/modules/juce_audio_processors/format_types/juce_LV2Resources.h @@ -29,6 +29,8 @@ #pragma once +#ifndef DOXYGEN + #include namespace juce @@ -10229,3 +10231,5 @@ to an instance of LV2_Extension_Data_Feature. }; } + +#endif diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h index 2a89b25b3..77544c7ae 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3Common.h +++ b/source/modules/juce_audio_processors/format_types/juce_VST3Common.h @@ -27,8 +27,6 @@ #ifndef DOXYGEN -#include - namespace juce { @@ -497,27 +495,20 @@ inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::Speak */ struct ChannelMapping { - explicit ChannelMapping (const AudioChannelSet& layout) - : ChannelMapping (layout, true) - { - } - ChannelMapping (const AudioChannelSet& layout, bool activeIn) - : indices (makeChannelIndices (layout)), - active (activeIn) - { - } + : indices (makeChannelIndices (layout)), active (activeIn) {} - explicit ChannelMapping (const AudioProcessor::Bus& juceBus) - : ChannelMapping (juceBus.getLastEnabledLayout(), juceBus.isEnabled()) - { - } + explicit ChannelMapping (const AudioChannelSet& layout) + : ChannelMapping (layout, true) {} + + explicit ChannelMapping (const AudioProcessor::Bus& bus) + : ChannelMapping (bus.getLastEnabledLayout(), bus.isEnabled()) {} int getJuceChannelForVst3Channel (int vst3Channel) const { return indices[(size_t) vst3Channel]; } size_t size() const { return indices.size(); } - void setActive (bool activeIn) { active = activeIn; } + void setActive (bool x) { active = x; } bool isActive() const { return active; } private: @@ -545,24 +536,106 @@ private: bool active = true; }; +class DynamicChannelMapping +{ +public: + DynamicChannelMapping (const AudioChannelSet& channelSet, bool active) + : set (channelSet), map (channelSet, active) {} + + explicit DynamicChannelMapping (const AudioChannelSet& channelSet) + : DynamicChannelMapping (channelSet, true) {} + + explicit DynamicChannelMapping (const AudioProcessor::Bus& bus) + : DynamicChannelMapping (bus.getLastEnabledLayout(), bus.isEnabled()) {} + + AudioChannelSet getAudioChannelSet() const { return set; } + int getJuceChannelForVst3Channel (int vst3Channel) const { return map.getJuceChannelForVst3Channel (vst3Channel); } + size_t size() const { return map.size(); } + + /* Returns true if the host has activated this bus. */ + bool isHostActive() const { return hostActive; } + /* Returns true if the AudioProcessor expects this bus to be active. */ + bool isClientActive() const { return map.isActive(); } + + void setHostActive (bool active) { hostActive = active; } + void setClientActive (bool active) { map.setActive (active); } + +private: + AudioChannelSet set; + ChannelMapping map; + bool hostActive = false; +}; + //============================================================================== inline auto& getAudioBusPointer (detail::Tag, Steinberg::Vst::AudioBusBuffers& data) { return data.channelBuffers32; } inline auto& getAudioBusPointer (detail::Tag, Steinberg::Vst::AudioBusBuffers& data) { return data.channelBuffers64; } -static inline int countUsedChannels (const std::vector& inputMap, - const std::vector& outputMap) +static inline int countUsedClientChannels (const std::vector& inputMap, + const std::vector& outputMap) { - const auto countUsedChannelsInVector = [] (const std::vector& map) + const auto countUsedChannelsInVector = [] (const std::vector& map) { return std::accumulate (map.begin(), map.end(), 0, [] (auto acc, const auto& item) { - return acc + (item.isActive() ? (int) item.size() : 0); + return acc + (item.isClientActive() ? (int) item.size() : 0); }); }; return jmax (countUsedChannelsInVector (inputMap), countUsedChannelsInVector (outputMap)); } +template +class ScratchBuffer +{ +public: + void setSize (int numChannels, int blockSize) + { + buffer.setSize (numChannels, blockSize); + } + + void clear() { channelCounter = 0; } + + auto* getNextChannelBuffer() { return buffer.getWritePointer (channelCounter++); } + + auto getArrayOfWritePointers() { return buffer.getArrayOfWritePointers(); } + +private: + AudioBuffer buffer; + int channelCounter = 0; +}; + +template +static int countValidBuses (Steinberg::Vst::AudioBusBuffers* buffers, int32 num) +{ + return (int) std::distance (buffers, std::find_if (buffers, buffers + num, [] (auto& buf) + { + return getAudioBusPointer (detail::Tag{}, buf) == nullptr && buf.numChannels > 0; + })); +} + +template +static bool validateLayouts (Iterator first, Iterator last, const std::vector& map) +{ + if ((size_t) std::distance (first, last) > map.size()) + return false; + + auto mapIterator = map.begin(); + + for (auto it = first; it != last; ++it, ++mapIterator) + { + auto** busPtr = getAudioBusPointer (detail::Tag{}, *it); + const auto anyChannelIsNull = std::any_of (busPtr, busPtr + it->numChannels, [] (auto* ptr) { return ptr == nullptr; }); + + // Null channels are allowed if the bus is inactive + if ((mapIterator->isHostActive() && anyChannelIsNull) || ((int) mapIterator->size() != it->numChannels)) + return false; + } + + // If the host didn't provide the full complement of buses, it must be because the other + // buses are all deactivated. + return std::none_of (mapIterator, map.end(), [] (const auto& item) { return item.isHostActive(); }); +} + /* The main purpose of this class is to remap a set of buffers provided by the VST3 host into an equivalent JUCE AudioBuffer using the JUCE channel layout/order. @@ -580,154 +653,110 @@ class ClientBufferMapperData public: void prepare (int numChannels, int blockSize) { - emptyBuffer.setSize (numChannels, blockSize); + scratchBuffer.setSize (numChannels, blockSize); channels.reserve ((size_t) jmin (128, numChannels)); } AudioBuffer getMappedBuffer (Steinberg::Vst::ProcessData& data, - const std::vector& inputMap, - const std::vector& outputMap) + const std::vector& inputMap, + const std::vector& outputMap) { - const auto usedChannels = countUsedChannels (inputMap, outputMap); + scratchBuffer.clear(); + channels.clear(); + + const auto usedChannels = countUsedClientChannels (inputMap, outputMap); // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here - const auto countValidBuses = [] (Steinberg::Vst::AudioBusBuffers* buffers, int32 num) - { - return int (std::distance (buffers, std::find_if (buffers, buffers + num, [] (Steinberg::Vst::AudioBusBuffers& buf) - { - return getAudioBusPointer (detail::Tag{}, buf) == nullptr && buf.numChannels > 0; - }))); - }; + const auto vstInputs = countValidBuses (data.inputs, data.numInputs); - const auto vstInputs = countValidBuses (data.inputs, data.numInputs); - const auto vstOutputs = countValidBuses (data.outputs, data.numOutputs); + if (! validateLayouts (data.inputs, data.inputs + vstInputs, inputMap)) + return getBlankBuffer (usedChannels, (int) data.numSamples); - if (! validateLayouts (data, vstInputs, inputMap, vstOutputs, outputMap)) - return clearOutputBuffersAndReturnBlankBuffer (data, vstOutputs, usedChannels); + setUpInputChannels (data, (size_t) vstInputs, scratchBuffer, inputMap, channels); + setUpOutputChannels (scratchBuffer, outputMap, channels); - // If we're here, then we know that the host has given us a usable layout - channels.clear(); + const auto channelPtr = channels.empty() ? scratchBuffer.getArrayOfWritePointers() + : channels.data(); - // Put the host-supplied output channel pointers into JUCE order - for (size_t i = 0; i < (size_t) vstOutputs; ++i) - { - const auto bus = getMappedOutputBus (data, outputMap, i); - channels.insert (channels.end(), bus.begin(), bus.end()); - } + return { channelPtr, (int) channels.size(), (int) data.numSamples }; + } - // For input channels that are < the total number of outputs channels, copy the input over - // the output buffer, at the appropriate JUCE channel index. - // For input channels that are >= the total number of output channels, add the input buffer - // pointer to the array of channel pointers. - for (size_t inputBus = 0, initialBusIndex = 0; inputBus < (size_t) vstInputs; ++inputBus) +private: + static void setUpInputChannels (Steinberg::Vst::ProcessData& data, + size_t vstInputs, + ScratchBuffer& scratchBuffer, + const std::vector& map, + std::vector& channels) + { + for (size_t busIndex = 0; busIndex < map.size(); ++busIndex) { - const auto& map = inputMap[inputBus]; + const auto mapping = map[busIndex]; - if (! map.isActive()) + if (! mapping.isClientActive()) continue; - auto** busPtr = getAudioBusPointer (detail::Tag{}, data.inputs[inputBus]); + const auto originalSize = channels.size(); - for (auto i = 0; i < (int) map.size(); ++i) - { - const auto destIndex = initialBusIndex + (size_t) map.getJuceChannelForVst3Channel (i); + for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) + channels.push_back (scratchBuffer.getNextChannelBuffer()); - channels.resize (jmax (channels.size(), destIndex + 1), nullptr); - - if (auto* dest = channels[destIndex]) - FloatVectorOperations::copy (dest, busPtr[i], (int) data.numSamples); - else - channels[destIndex] = busPtr[i]; + if (mapping.isHostActive() && busIndex < vstInputs) + { + auto** busPtr = getAudioBusPointer (detail::Tag{}, data.inputs[busIndex]); + + for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) + { + FloatVectorOperations::copy (channels[(size_t) mapping.getJuceChannelForVst3Channel ((int) channelIndex) + originalSize], + busPtr[channelIndex], + (size_t) data.numSamples); + } + } + else + { + for (size_t channelIndex = 0; channelIndex < mapping.size(); ++channelIndex) + FloatVectorOperations::clear (channels[originalSize + channelIndex], (size_t) data.numSamples); } - - initialBusIndex += map.size(); } - - return { channels.data(), (int) channels.size(), (int) data.numSamples }; } -private: - AudioBuffer clearOutputBuffersAndReturnBlankBuffer (Steinberg::Vst::ProcessData& data, int vstOutputs, int usedChannels) + static void setUpOutputChannels (ScratchBuffer& scratchBuffer, + const std::vector& map, + std::vector& channels) { - // The host is ignoring the bus layout we requested, so we can't process sensibly! - jassertfalse; - - // Clear all output channels - std::for_each (data.outputs, data.outputs + vstOutputs, [&data] (auto& bus) + for (size_t i = 0, initialBusIndex = 0; i < (size_t) map.size(); ++i) { - auto** busPtr = getAudioBusPointer (detail::Tag{}, bus); - std::for_each (busPtr, busPtr + bus.numChannels, [&data] (auto* ptr) - { - if (ptr != nullptr) - FloatVectorOperations::clear (ptr, (int) data.numSamples); - }); - }); - - // Return a silent buffer for the AudioProcessor to process - emptyBuffer.clear(); - - return { emptyBuffer.getArrayOfWritePointers(), - jmin (emptyBuffer.getNumChannels(), usedChannels), - data.numSamples }; - } - - std::vector getMappedOutputBus (Steinberg::Vst::ProcessData& data, - const std::vector& maps, - size_t index) const - { - const auto& map = maps[index]; - - if (! map.isActive()) - return {}; - - auto** busPtr = getAudioBusPointer (detail::Tag{}, data.outputs[index]); - - std::vector result (map.size(), nullptr); + const auto& mapping = map[i]; - for (auto i = 0; i < (int) map.size(); ++i) - result[(size_t) map.getJuceChannelForVst3Channel (i)] = busPtr[i]; + if (mapping.isClientActive()) + { + for (size_t j = 0; j < mapping.size(); ++j) + { + if (channels.size() <= initialBusIndex + j) + channels.push_back (scratchBuffer.getNextChannelBuffer()); + } - return result; + initialBusIndex += mapping.size(); + } + } } - template - static bool validateLayouts (Iterator first, Iterator last, const std::vector& map) + AudioBuffer getBlankBuffer (int usedChannels, int usedSamples) { - if ((size_t) std::distance (first, last) > map.size()) - return false; - - auto mapIterator = map.begin(); + // The host is ignoring the bus layout we requested, so we can't process sensibly! + jassertfalse; - for (auto it = first; it != last; ++it, ++mapIterator) + // Return a silent buffer for the AudioProcessor to process + for (auto i = 0; i < usedChannels; ++i) { - auto** busPtr = getAudioBusPointer (detail::Tag{}, *it); - const auto anyChannelIsNull = std::any_of (busPtr, busPtr + it->numChannels, [] (auto* ptr) { return ptr == nullptr; }); - - if (anyChannelIsNull || ((int) mapIterator->size() != it->numChannels)) - return false; + channels.push_back (scratchBuffer.getNextChannelBuffer()); + FloatVectorOperations::clear (channels.back(), usedSamples); } - // If the host didn't provide the full complement of buses, it must be because the other - // buses are all deactivated. - return std::none_of (mapIterator, map.end(), [] (const auto& item) { return item.isActive(); }); - } - - static bool validateLayouts (Steinberg::Vst::ProcessData& data, - int numInputs, - const std::vector& inputMap, - int numOutputs, - const std::vector& outputMap) - { - - // The plug-in should only process an activated bus. - // The host could provide fewer busses in the process call if the last busses are not activated. - - return validateLayouts (data.inputs, data.inputs + numInputs, inputMap) - && validateLayouts (data.outputs, data.outputs + numOutputs, outputMap); + return { channels.data(), (int) channels.size(), usedSamples }; } std::vector channels; - AudioBuffer emptyBuffer; + ScratchBuffer scratchBuffer; }; //============================================================================== @@ -749,65 +778,213 @@ private: class ClientBufferMapper { public: - void prepare (const AudioProcessor& processor, int blockSize) + void updateFromProcessor (const AudioProcessor& processor) { struct Pair { - std::vector& map; + std::vector& map; bool isInput; }; for (const auto& pair : { Pair { inputMap, true }, Pair { outputMap, false } }) { - pair.map.clear(); - - for (auto i = 0; i < processor.getBusCount (pair.isInput); ++i) - pair.map.emplace_back (*processor.getBus (pair.isInput, i)); + if (pair.map.empty()) + { + for (auto i = 0; i < processor.getBusCount (pair.isInput); ++i) + pair.map.emplace_back (*processor.getBus (pair.isInput, i)); + } + else + { + // The number of buses cannot change after creating a VST3 plugin! + jassert ((size_t) processor.getBusCount (pair.isInput) == pair.map.size()); + + for (size_t i = 0; i < (size_t) processor.getBusCount (pair.isInput); ++i) + { + pair.map[i] = [&] + { + DynamicChannelMapping replacement { *processor.getBus (pair.isInput, (int) i) }; + replacement.setHostActive (pair.map[i].isHostActive()); + return replacement; + }(); + } + } } + } - const auto findMaxNumChannels = [&] (bool isInput) + void prepare (int blockSize) + { + const auto findNumChannelsWhenAllBusesEnabled = [] (const auto& map) { - auto sum = 0; - - for (auto i = 0; i < processor.getBusCount (isInput); ++i) - sum += processor.getBus (isInput, i)->getLastEnabledLayout().size(); - - return sum; + return std::accumulate (map.cbegin(), map.cend(), 0, [] (auto acc, const auto& item) + { + return acc + (int) item.size(); + }); }; - const auto numChannels = jmax (findMaxNumChannels (true), findMaxNumChannels (false)); + const auto numChannels = jmax (findNumChannelsWhenAllBusesEnabled (inputMap), + findNumChannelsWhenAllBusesEnabled (outputMap)); floatData .prepare (numChannels, blockSize); doubleData.prepare (numChannels, blockSize); } - void setInputBusActive (size_t bus, bool state) + void updateActiveClientBuses (const AudioProcessor::BusesLayout& clientBuses) { - if (bus < inputMap.size()) - inputMap[bus].setActive (state); + if ( (size_t) clientBuses.inputBuses .size() != inputMap .size() + || (size_t) clientBuses.outputBuses.size() != outputMap.size()) + { + jassertfalse; + return; + } + + const auto sync = [] (auto& map, auto& client) + { + for (size_t i = 0; i < map.size(); ++i) + { + jassert (client[(int) i] == AudioChannelSet::disabled() || client[(int) i] == map[i].getAudioChannelSet()); + map[i].setClientActive (client[(int) i] != AudioChannelSet::disabled()); + } + }; + + sync (inputMap, clientBuses.inputBuses); + sync (outputMap, clientBuses.outputBuses); } - void setOutputBusActive (size_t bus, bool state) + void setInputBusHostActive (size_t bus, bool state) { setHostActive (inputMap, bus, state); } + void setOutputBusHostActive (size_t bus, bool state) { setHostActive (outputMap, bus, state); } + + auto& getData (detail::Tag) { return floatData; } + auto& getData (detail::Tag) { return doubleData; } + + AudioChannelSet getRequestedLayoutForInputBus (size_t bus) const { - if (bus < outputMap.size()) - outputMap[bus].setActive (state); + return getRequestedLayoutForBus (inputMap, bus); } - template - AudioBuffer getJuceLayoutForVst3Buffer (detail::Tag, Steinberg::Vst::ProcessData& data) + AudioChannelSet getRequestedLayoutForOutputBus (size_t bus) const { - return getData (detail::Tag{}).getMappedBuffer (data, inputMap, outputMap); + return getRequestedLayoutForBus (outputMap, bus); } + const std::vector& getInputMap() const { return inputMap; } + const std::vector& getOutputMap() const { return outputMap; } + private: - auto& getData (detail::Tag) { return floatData; } - auto& getData (detail::Tag) { return doubleData; } + static void setHostActive (std::vector& map, size_t bus, bool state) + { + if (bus < map.size()) + map[bus].setHostActive (state); + } + + static AudioChannelSet getRequestedLayoutForBus (const std::vector& map, size_t bus) + { + if (bus < map.size() && map[bus].isHostActive()) + return map[bus].getAudioChannelSet(); + + return AudioChannelSet::disabled(); + } ClientBufferMapperData floatData; ClientBufferMapperData doubleData; - std::vector inputMap; - std::vector outputMap; + std::vector inputMap; + std::vector outputMap; +}; + +//============================================================================== +/* Holds a buffer in the JUCE channel layout, and a reference to a Vst ProcessData struct, and + copies each JUCE channel to the appropriate host output channel when this object goes + out of scope. +*/ +template +class ClientRemappedBuffer +{ +public: + ClientRemappedBuffer (ClientBufferMapperData& mapperData, + const std::vector* inputMapIn, + const std::vector* outputMapIn, + Steinberg::Vst::ProcessData& hostData) + : buffer (mapperData.getMappedBuffer (hostData, *inputMapIn, *outputMapIn)), + outputMap (outputMapIn), + data (hostData) + {} + + ClientRemappedBuffer (ClientBufferMapper& mapperIn, Steinberg::Vst::ProcessData& hostData) + : ClientRemappedBuffer (mapperIn.getData (detail::Tag{}), + &mapperIn.getInputMap(), + &mapperIn.getOutputMap(), + hostData) + {} + + ~ClientRemappedBuffer() + { + // WaveLab workaround: This host may report the wrong number of inputs/outputs so re-count here + const auto vstOutputs = (size_t) countValidBuses (data.outputs, data.numOutputs); + + if (validateLayouts (data.outputs, data.outputs + vstOutputs, *outputMap)) + copyToHostOutputBuses (vstOutputs); + else + clearHostOutputBuses (vstOutputs); + } + + AudioBuffer buffer; + +private: + void copyToHostOutputBuses (size_t vstOutputs) const + { + for (size_t i = 0, juceBusOffset = 0; i < outputMap->size(); ++i) + { + const auto& mapping = (*outputMap)[i]; + + if (mapping.isHostActive() && i < vstOutputs) + { + auto& bus = data.outputs[i]; + + if (mapping.isClientActive()) + { + for (size_t j = 0; j < mapping.size(); ++j) + { + auto* hostChannel = getAudioBusPointer (detail::Tag{}, bus)[j]; + const auto juceChannel = juceBusOffset + (size_t) mapping.getJuceChannelForVst3Channel ((int) j); + FloatVectorOperations::copy (hostChannel, buffer.getReadPointer ((int) juceChannel), (size_t) data.numSamples); + } + } + else + { + for (size_t j = 0; j < mapping.size(); ++j) + { + auto* hostChannel = getAudioBusPointer (detail::Tag{}, bus)[j]; + FloatVectorOperations::clear (hostChannel, (size_t) data.numSamples); + } + } + } + + if (mapping.isClientActive()) + juceBusOffset += mapping.size(); + } + } + + void clearHostOutputBuses (size_t vstOutputs) const + { + // The host provided us with an unexpected bus layout. + jassertfalse; + + std::for_each (data.outputs, data.outputs + vstOutputs, [this] (auto& bus) + { + auto** busPtr = getAudioBusPointer (detail::Tag{}, bus); + std::for_each (busPtr, busPtr + bus.numChannels, [this] (auto* ptr) + { + if (ptr != nullptr) + FloatVectorOperations::clear (ptr, (int) data.numSamples); + }); + }); + } + + const std::vector* outputMap = nullptr; + Steinberg::Vst::ProcessData& data; + + JUCE_DECLARE_NON_COPYABLE (ClientRemappedBuffer) + JUCE_DECLARE_NON_MOVEABLE (ClientRemappedBuffer) }; //============================================================================== diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp index 007597914..31bf6ff48 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat.cpp @@ -94,6 +94,12 @@ static int warnOnFailureIfImplemented (int result) noexcept #define warnOnFailureIfImplemented(x) x #endif +enum class Direction { input, output }; +enum class MediaKind { audio, event }; + +static Vst::MediaType toVstType (MediaKind x) { return x == MediaKind::audio ? Vst::kAudio : Vst::kEvent; } +static Vst::BusDirection toVstType (Direction x) { return x == Direction::input ? Vst::kInput : Vst::kOutput; } + static std::vector getAllParamIDs (Vst::IEditController& controller) { std::vector result; @@ -211,58 +217,53 @@ static void createPluginDescription (PluginDescription& description, } static int getNumSingleDirectionBusesFor (Vst::IComponent* component, - bool checkInputs, - bool checkAudioChannels) + MediaKind kind, + Direction direction) { jassert (component != nullptr); - - return (int) component->getBusCount (checkAudioChannels ? Vst::kAudio : Vst::kEvent, - checkInputs ? Vst::kInput : Vst::kOutput); + JUCE_ASSERT_MESSAGE_THREAD + return (int) component->getBusCount (toVstType (kind), toVstType (direction)); } /** Gives the total number of channels for a particular type of bus direction and media type */ -static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, - bool checkInputs, - bool checkAudioChannels) +static int getNumSingleDirectionChannelsFor (Vst::IComponent* component, Direction busDirection) { jassert (component != nullptr); + JUCE_ASSERT_MESSAGE_THREAD - const Vst::BusDirections direction = checkInputs ? Vst::kInput : Vst::kOutput; - const Vst::MediaTypes mediaType = checkAudioChannels ? Vst::kAudio : Vst::kEvent; - const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); + const auto direction = toVstType (busDirection); + const Steinberg::int32 numBuses = component->getBusCount (Vst::kAudio, direction); int numChannels = 0; for (Steinberg::int32 i = numBuses; --i >= 0;) { Vst::BusInfo busInfo; - warnOnFailure (component->getBusInfo (mediaType, direction, i, busInfo)); + warnOnFailure (component->getBusInfo (Vst::kAudio, direction, i, busInfo)); numChannels += ((busInfo.flags & Vst::BusInfo::kDefaultActive) != 0 ? (int) busInfo.channelCount : 0); } return numChannels; } -static void setStateForAllBusesOfType (Vst::IComponent* component, - bool state, - bool activateInputs, - bool activateAudioChannels) +static void setStateForAllEventBuses (Vst::IComponent* component, + bool state, + Direction busDirection) { jassert (component != nullptr); + JUCE_ASSERT_MESSAGE_THREAD - const Vst::BusDirections direction = activateInputs ? Vst::kInput : Vst::kOutput; - const Vst::MediaTypes mediaType = activateAudioChannels ? Vst::kAudio : Vst::kEvent; - const Steinberg::int32 numBuses = component->getBusCount (mediaType, direction); + const auto direction = toVstType (busDirection); + const Steinberg::int32 numBuses = component->getBusCount (Vst::kEvent, direction); for (Steinberg::int32 i = numBuses; --i >= 0;) - warnOnFailure (component->activateBus (mediaType, direction, i, state)); + warnOnFailure (component->activateBus (Vst::kEvent, direction, i, state)); } //============================================================================== static void toProcessContext (Vst::ProcessContext& context, AudioPlayHead* playHead, - double sampleRate, - const uint64_t* hostTimeNs) + double sampleRate) { jassert (sampleRate > 0.0); //Must always be valid, as stated by the VST3 SDK @@ -270,59 +271,71 @@ static void toProcessContext (Vst::ProcessContext& context, zerostruct (context); context.sampleRate = sampleRate; - auto& fr = context.frameRate; - if (playHead != nullptr) + const auto position = playHead != nullptr ? playHead->getPosition() + : nullopt; + + if (position.hasValue()) { - AudioPlayHead::CurrentPositionInfo position; - playHead->getCurrentPosition (position); + if (const auto timeInSamples = position->getTimeInSamples()) + context.projectTimeSamples = *timeInSamples; + else + jassertfalse; // The time in samples *must* be valid. - context.projectTimeSamples = position.timeInSamples; // Must always be valid, as stated by the VST3 SDK - context.projectTimeMusic = position.ppqPosition; // Does not always need to be valid... - context.tempo = position.bpm; - context.timeSigNumerator = position.timeSigNumerator; - context.timeSigDenominator = position.timeSigDenominator; - context.barPositionMusic = position.ppqPositionOfLastBarStart; - context.cycleStartMusic = position.ppqLoopStart; - context.cycleEndMusic = position.ppqLoopEnd; + if (const auto tempo = position->getBpm()) + { + context.state |= ProcessContext::kTempoValid; + context.tempo = *tempo; + } - context.frameRate.framesPerSecond = (Steinberg::uint32) position.frameRate.getBaseRate(); - context.frameRate.flags = (Steinberg::uint32) ((position.frameRate.isDrop() ? FrameRate::kDropRate : 0) - | (position.frameRate.isPullDown() ? FrameRate::kPullDownRate : 0)); + if (const auto loop = position->getLoopPoints()) + { + context.state |= ProcessContext::kCycleValid; + context.cycleStartMusic = loop->ppqStart; + context.cycleEndMusic = loop->ppqEnd; + } - if (position.isPlaying) context.state |= ProcessContext::kPlaying; - if (position.isRecording) context.state |= ProcessContext::kRecording; - if (position.isLooping) context.state |= ProcessContext::kCycleActive; - } - else - { - context.tempo = 120.0; - context.timeSigNumerator = 4; - context.timeSigDenominator = 4; - fr.framesPerSecond = 30; - fr.flags = 0; - } + if (const auto sig = position->getTimeSignature()) + { + context.state |= ProcessContext::kTimeSigValid; + context.timeSigNumerator = sig->numerator; + context.timeSigDenominator = sig->denominator; + } - if (context.projectTimeMusic >= 0.0) context.state |= ProcessContext::kProjectTimeMusicValid; - if (context.barPositionMusic >= 0.0) context.state |= ProcessContext::kBarPositionValid; - if (context.tempo > 0.0) context.state |= ProcessContext::kTempoValid; - if (context.frameRate.framesPerSecond > 0) context.state |= ProcessContext::kSmpteValid; + if (const auto pos = position->getPpqPosition()) + { + context.state |= ProcessContext::kProjectTimeMusicValid; + context.projectTimeMusic = *pos; + } - if (context.cycleStartMusic >= 0.0 - && context.cycleEndMusic > 0.0 - && context.cycleEndMusic > context.cycleStartMusic) - { - context.state |= ProcessContext::kCycleValid; - } + if (const auto barStart = position->getPpqPositionOfLastBarStart()) + { + context.state |= ProcessContext::kBarPositionValid; + context.barPositionMusic = *barStart; + } + + if (const auto frameRate = position->getFrameRate()) + { + if (const auto offset = position->getEditOriginTime()) + { + context.state |= ProcessContext::kSmpteValid; + context.smpteOffsetSubframes = (Steinberg::int32) (80.0 * *offset * frameRate->getEffectiveRate()); + context.frameRate.framesPerSecond = (Steinberg::uint32) frameRate->getBaseRate(); + context.frameRate.flags = (Steinberg::uint32) ((frameRate->isDrop() ? FrameRate::kDropRate : 0) + | (frameRate->isPullDown() ? FrameRate::kPullDownRate : 0)); + } + } - if (context.timeSigNumerator > 0 && context.timeSigDenominator > 0) - context.state |= ProcessContext::kTimeSigValid; + if (const auto hostTime = position->getHostTimeNs()) + { + context.state |= ProcessContext::kSystemTimeValid; + context.systemTime = (int64_t) *hostTime; + jassert (context.systemTime >= 0); + } - if (hostTimeNs != nullptr) - { - context.systemTime = (int64_t) *hostTimeNs; - jassert (context.systemTime >= 0); - context.state |= ProcessContext::kSystemTimeValid; + if (position->getIsPlaying()) context.state |= ProcessContext::kPlaying; + if (position->getIsRecording()) context.state |= ProcessContext::kRecording; + if (position->getIsLooping()) context.state |= ProcessContext::kCycleActive; } } @@ -881,8 +894,8 @@ struct DescriptionFactory { if (component->initialize (vst3HostContext->getFUnknown()) == kResultOk) { - auto numInputs = getNumSingleDirectionChannelsFor (component, true, true); - auto numOutputs = getNumSingleDirectionChannelsFor (component, false, true); + auto numInputs = getNumSingleDirectionChannelsFor (component, Direction::input); + auto numOutputs = getNumSingleDirectionChannelsFor (component, Direction::output); createPluginDescription (desc, file, companyName, name, info, info2.get(), infoW.get(), numInputs, numOutputs); @@ -1479,12 +1492,6 @@ struct VST3PluginWindow : public AudioProcessorEditor, view = nullptr; } - // FIXME - void* getPlatformSpecificData() override - { - return view; - } - #if JUCE_LINUX || JUCE_BSD Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID queryIid, void** obj) override { @@ -1819,9 +1826,6 @@ struct VST3ComponentHolder terminate(); } - // transfers ownership to the plugin instance! - AudioPluginInstance* createPluginInstance(); - bool isIComponentAlsoIEditController() const { if (component == nullptr) @@ -1915,12 +1919,12 @@ struct VST3ComponentHolder Vst::BusInfo bus; int totalNumInputChannels = 0, totalNumOutputChannels = 0; - int n = component->getBusCount(Vst::kAudio, Vst::kInput); + int n = component->getBusCount (Vst::kAudio, Vst::kInput); for (int i = 0; i < n; ++i) if (component->getBusInfo (Vst::kAudio, Vst::kInput, i, bus) == kResultOk) totalNumInputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); - n = component->getBusCount(Vst::kAudio, Vst::kOutput); + n = component->getBusCount (Vst::kAudio, Vst::kOutput); for (int i = 0; i < n; ++i) if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk) totalNumOutputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); @@ -2307,22 +2311,21 @@ public: const Steinberg::int32 vstParamIndex; const Steinberg::Vst::ParamID paramID; const bool automatable; - const bool discrete = getNumSteps() != AudioProcessor::getDefaultNumParameterSteps(); const int numSteps = [&] { auto stepCount = getParameterInfo().stepCount; return stepCount == 0 ? AudioProcessor::getDefaultNumParameterSteps() : stepCount + 1; }(); + const bool discrete = getNumSteps() != AudioProcessor::getDefaultNumParameterSteps(); }; //============================================================================== - VST3PluginInstance (VST3ComponentHolder* componentHolder) + explicit VST3PluginInstance (std::unique_ptr componentHolder) : AudioPluginInstance (getBusProperties (componentHolder->component)), - holder (componentHolder), - midiInputs (new MidiEventList()), - midiOutputs (new MidiEventList()) + holder (std::move (componentHolder)) { + jassert (holder->isComponentInitialised); holder->host->setPlugin (this); } @@ -2787,12 +2790,9 @@ public: // call releaseResources first! jassert (! isActive); - bool result = syncBusLayouts (layouts); - - // didn't succeed? Make sure it's back in its original state - if (! result) - syncBusLayouts (getBusesLayout()); - + const auto previousLayout = getBusesLayout(); + const auto result = syncBusLayouts (layouts); + syncBusLayouts (previousLayout); return result; } @@ -2857,14 +2857,15 @@ public: }; //============================================================================== - String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const + String getChannelName (int channelIndex, Direction direction) const { - auto numBuses = getNumSingleDirectionBusesFor (holder->component, forInput, forAudioChannel); + auto numBuses = getNumSingleDirectionBusesFor (holder->component, MediaKind::audio, direction); + int numCountedChannels = 0; for (int i = 0; i < numBuses; ++i) { - auto busInfo = getBusInfo (forInput, forAudioChannel, i); + auto busInfo = getBusInfo (MediaKind::audio, direction, i); numCountedChannels += busInfo.channelCount; @@ -2875,25 +2876,25 @@ public: return {}; } - const String getInputChannelName (int channelIndex) const override { return getChannelName (channelIndex, true, true); } - const String getOutputChannelName (int channelIndex) const override { return getChannelName (channelIndex, false, true); } + const String getInputChannelName (int channelIndex) const override { return getChannelName (channelIndex, Direction::input); } + const String getOutputChannelName (int channelIndex) const override { return getChannelName (channelIndex, Direction::output); } bool isInputChannelStereoPair (int channelIndex) const override { int busIdx; return getOffsetInBusBufferForAbsoluteChannelIndex (true, channelIndex, busIdx) >= 0 - && getBusInfo (true, true, busIdx).channelCount == 2; + && getBusInfo (MediaKind::audio, Direction::input, busIdx).channelCount == 2; } bool isOutputChannelStereoPair (int channelIndex) const override { int busIdx; return getOffsetInBusBufferForAbsoluteChannelIndex (false, channelIndex, busIdx) >= 0 - && getBusInfo (false, true, busIdx).channelCount == 2; + && getBusInfo (MediaKind::audio, Direction::output, busIdx).channelCount == 2; } - bool acceptsMidi() const override { return getNumSingleDirectionBusesFor (holder->component, true, false) > 0; } - bool producesMidi() const override { return getNumSingleDirectionBusesFor (holder->component, false, false) > 0; } + bool acceptsMidi() const override { return hasMidiInput; } + bool producesMidi() const override { return hasMidiOutput; } //============================================================================== AudioProcessorParameter* getBypassParameter() const override { return bypassParam; } @@ -3191,9 +3192,11 @@ private: CachedParamValues cachedParamValues; VSTComSmartPtr inputParameterChanges { new ParameterChanges }; VSTComSmartPtr outputParameterChanges { new ParameterChanges }; - VSTComSmartPtr midiInputs, midiOutputs; + VSTComSmartPtr midiInputs { new MidiEventList }, midiOutputs { new MidiEventList }; Vst::ProcessContext timingInfo; //< Only use this in processBlock()! bool isControllerInitialised = false, isActive = false, lastProcessBlockCallWasBypass = false; + const bool hasMidiInput = getNumSingleDirectionBusesFor (holder->component, MediaKind::event, Direction::input) > 0, + hasMidiOutput = getNumSingleDirectionBusesFor (holder->component, MediaKind::event, Direction::output) > 0; VST3Parameter* bypassParam = nullptr; //============================================================================== @@ -3328,8 +3331,8 @@ private: void setStateForAllMidiBuses (bool newState) { - setStateForAllBusesOfType (holder->component, newState, true, false); // Activate/deactivate MIDI inputs - setStateForAllBusesOfType (holder->component, newState, false, false); // Activate/deactivate MIDI outputs + setStateForAllEventBuses (holder->component, newState, Direction::input); + setStateForAllEventBuses (holder->component, newState, Direction::output); } std::vector createChannelMappings (bool isInput) const @@ -3395,11 +3398,11 @@ private: } //============================================================================== - Vst::BusInfo getBusInfo (bool forInput, bool forAudio, int index = 0) const + Vst::BusInfo getBusInfo (MediaKind kind, Direction direction, int index = 0) const { Vst::BusInfo busInfo; - busInfo.mediaType = forAudio ? Vst::kAudio : Vst::kEvent; - busInfo.direction = forInput ? Vst::kInput : Vst::kOutput; + busInfo.mediaType = toVstType (kind); + busInfo.direction = toVstType (direction); busInfo.channelCount = 0; holder->component->getBusInfo (busInfo.mediaType, busInfo.direction, @@ -3474,7 +3477,7 @@ private: void updateTimingInformation (Vst::ProcessData& destination, double processSampleRate) { - toProcessContext (timingInfo, getPlayHead(), processSampleRate, getHostTimeNs()); + toProcessContext (timingInfo, getPlayHead(), processSampleRate); destination.processContext = &timingInfo; } @@ -3577,17 +3580,6 @@ private: JUCE_END_IGNORE_WARNINGS_MSVC -//============================================================================== -AudioPluginInstance* VST3ComponentHolder::createPluginInstance() -{ - if (! initialise()) - return nullptr; - - auto* plugin = new VST3PluginInstance (this); - host->setPlugin (plugin); - return plugin; -} - //============================================================================== tresult VST3HostContext::beginEdit (Vst::ParamID paramID) { @@ -3828,38 +3820,48 @@ void VST3PluginFormat::createARAFactoryAsync (const PluginDescription& descripti callback ({ ARAFactoryWrapper { ::juce::getARAFactory (pluginFactory, pluginName) }, {} }); } -void VST3PluginFormat::createPluginInstance (const PluginDescription& description, - double, int, PluginCreationCallback callback) +static std::unique_ptr createVST3Instance (VST3PluginFormat& format, + const PluginDescription& description) { - std::unique_ptr result; + if (! format.fileMightContainThisPluginType (description.fileOrIdentifier)) + return nullptr; + + const File file { description.fileOrIdentifier }; - if (fileMightContainThisPluginType (description.fileOrIdentifier)) + struct ScopedWorkingDirectory { - File file (description.fileOrIdentifier); + ~ScopedWorkingDirectory() { previousWorkingDirectory.setAsCurrentWorkingDirectory(); } + File previousWorkingDirectory = File::getCurrentWorkingDirectory(); + }; - auto previousWorkingDirectory = File::getCurrentWorkingDirectory(); - file.getParentDirectory().setAsCurrentWorkingDirectory(); + const ScopedWorkingDirectory scope; + file.getParentDirectory().setAsCurrentWorkingDirectory(); - if (const VST3ModuleHandle::Ptr module = VST3ModuleHandle::findOrCreateModule (file, description)) - { - std::unique_ptr holder (new VST3ComponentHolder (module)); + const VST3ModuleHandle::Ptr module { VST3ModuleHandle::findOrCreateModule (file, description) }; - if (holder->initialise()) - { - result.reset (new VST3PluginInstance (holder.release())); + if (module == nullptr) + return nullptr; - if (! result->initialise()) - result.reset(); - } - } + auto holder = std::make_unique (module); - previousWorkingDirectory.setAsCurrentWorkingDirectory(); - } + if (! holder->initialise()) + return nullptr; - String errorMsg; + auto instance = std::make_unique (std::move (holder)); + + if (! instance->initialise()) + return nullptr; + + return instance; +} + +void VST3PluginFormat::createPluginInstance (const PluginDescription& description, + double, int, PluginCreationCallback callback) +{ + auto result = createVST3Instance (*this, description); - if (result == nullptr) - errorMsg = TRANS ("Unable to load XXX plug-in file").replace ("XXX", "VST-3"); + const auto errorMsg = result == nullptr ? TRANS ("Unable to load XXX plug-in file").replace ("XXX", "VST-3") + : String(); callback (std::move (result), errorMsg); } diff --git a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp index c2274a3ff..305a705cb 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VST3PluginFormat_test.cpp @@ -43,7 +43,6 @@ public: { ChannelMapping map (AudioChannelSet::stereo()); expect (map.size() == 2); - expect (map.isActive() == true); expect (map.getJuceChannelForVst3Channel (0) == 0); // L -> left expect (map.getJuceChannelForVst3Channel (1) == 1); // R -> right @@ -53,7 +52,6 @@ public: { ChannelMapping map (AudioChannelSet::create9point1point6()); expect (map.size() == 16); - expect (map.isActive() == true); // VST3 order is: // L @@ -115,8 +113,8 @@ public: ClientBufferMapperData remapper; remapper.prepare (2, blockSize * 2); - const std::vector emptyBuses; - const std::vector stereoBus { ChannelMapping { AudioChannelSet::stereo() } }; + const std::vector emptyBuses; + const std::vector stereoBus { DynamicChannelMapping { AudioChannelSet::stereo() } }; TestBuffers testBuffers { blockSize }; @@ -127,12 +125,17 @@ public: for (const auto& config : { Config { stereoBus, stereoBus }, Config { emptyBuses, stereoBus }, Config { stereoBus, emptyBuses } }) { testBuffers.init(); - const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); - expect (remapped.getNumChannels() == config.getNumChannels()); - expect (remapped.getNumSamples() == blockSize); - for (auto i = 0; i < remapped.getNumChannels(); ++i) - expect (allMatch (remapped, i, 0.0f)); + { + const ClientRemappedBuffer scopedBuffer { remapper, &config.ins, &config.outs, data }; + auto& remapped = scopedBuffer.buffer; + + expect (remapped.getNumChannels() == config.getNumChannels()); + expect (remapped.getNumSamples() == blockSize); + + for (auto i = 0; i < remapped.getNumChannels(); ++i) + expect (allMatch (remapped, i, 0.0f)); + } expect (! testBuffers.isClear (0)); expect (! testBuffers.isClear (1)); @@ -148,10 +151,10 @@ public: ClientBufferMapperData remapper; remapper.prepare (3, blockSize * 2); - const std::vector noBus; - const std::vector oneBus { ChannelMapping { AudioChannelSet::mono() } }; - const std::vector twoBuses { ChannelMapping { AudioChannelSet::mono() }, - ChannelMapping { AudioChannelSet::stereo() } }; + const std::vector noBus; + const std::vector oneBus { DynamicChannelMapping { AudioChannelSet::mono() } }; + const std::vector twoBuses { DynamicChannelMapping { AudioChannelSet::mono() }, + DynamicChannelMapping { AudioChannelSet::stereo() } }; TestBuffers testBuffers { blockSize }; @@ -166,12 +169,20 @@ public: Config { twoBuses, twoBuses } }) { testBuffers.init(); - const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); - expect (remapped.getNumChannels() == config.getNumChannels()); - expect (remapped.getNumSamples() == blockSize); - for (auto i = 0; i < remapped.getNumChannels(); ++i) - expect (allMatch (remapped, i, 0.0f)); + { + const ClientRemappedBuffer scopedBuffer { remapper, &config.ins, &config.outs, data }; + auto& remapped = scopedBuffer.buffer; + + expect (remapped.getNumChannels() == config.getNumChannels()); + expect (remapped.getNumSamples() == blockSize); + + // The remapped buffer will only be cleared if the host's input layout does not + // match the client's input layout. + if (config.ins.size() != 1) + for (auto i = 0; i < remapped.getNumChannels(); ++i) + expect (allMatch (remapped, i, 0.0f)); + } expect (! testBuffers.isClear (0)); expect (testBuffers.isClear (1)); @@ -183,8 +194,8 @@ public: ClientBufferMapperData remapper; remapper.prepare (3, blockSize * 2); - const std::vector monoBus { ChannelMapping { AudioChannelSet::mono() } }; - const std::vector stereoBus { ChannelMapping { AudioChannelSet::stereo() } }; + const std::vector monoBus { DynamicChannelMapping { AudioChannelSet::mono() } }; + const std::vector stereoBus { DynamicChannelMapping { AudioChannelSet::stereo() } }; TestBuffers testBuffers { blockSize }; @@ -197,12 +208,20 @@ public: Config { monoBus, monoBus } }) { testBuffers.init(); - const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); - expect (remapped.getNumChannels() == config.getNumChannels()); - expect (remapped.getNumSamples() == blockSize); - for (auto i = 0; i < remapped.getNumChannels(); ++i) - expect (allMatch (remapped, i, 0.0f)); + { + const ClientRemappedBuffer scopedBuffer { remapper, &config.ins, &config.outs, data }; + auto& remapped = scopedBuffer.buffer; + + expect (remapped.getNumChannels() == config.getNumChannels()); + expect (remapped.getNumSamples() == blockSize); + + // The remapped buffer will only be cleared if the host's input layout does not + // match the client's input layout. + if (config.ins.front().size() != 1) + for (auto i = 0; i < remapped.getNumChannels(); ++i) + expect (allMatch (remapped, i, 0.0f)); + } expect (! testBuffers.isClear (0)); expect (testBuffers.isClear (1)); @@ -215,10 +234,10 @@ public: ClientBufferMapperData remapper; remapper.prepare (20, blockSize * 2); - const Config config { { ChannelMapping { AudioChannelSet::mono() }, - ChannelMapping { AudioChannelSet::create5point1() } }, - { ChannelMapping { AudioChannelSet::stereo() }, - ChannelMapping { AudioChannelSet::create7point1() } } }; + const Config config { { DynamicChannelMapping { AudioChannelSet::mono() }, + DynamicChannelMapping { AudioChannelSet::create5point1() } }, + { DynamicChannelMapping { AudioChannelSet::stereo() }, + DynamicChannelMapping { AudioChannelSet::create7point1() } } }; TestBuffers testBuffers { blockSize }; @@ -228,45 +247,54 @@ public: auto data = makeProcessData (blockSize, ins, outs); testBuffers.init(); - const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); - - expect (remapped.getNumChannels() == 10); - - // Data from the input channels is copied to the correct channels of the remapped buffer - expect (allMatch (remapped, 0, 1.0f)); - expect (allMatch (remapped, 1, 2.0f)); - expect (allMatch (remapped, 2, 3.0f)); - expect (allMatch (remapped, 3, 4.0f)); - expect (allMatch (remapped, 4, 5.0f)); - expect (allMatch (remapped, 5, 6.0f)); - expect (allMatch (remapped, 6, 7.0f)); - // These channels are output-only, so they keep whatever data was previously on that output channel - expect (allMatch (remapped, 7, 17.0f)); - expect (allMatch (remapped, 8, 14.0f)); - expect (allMatch (remapped, 9, 15.0f)); - - // Channel pointers from the VST3 buffer are used - expect (remapped.getReadPointer (0) == testBuffers.get (7)); - expect (remapped.getReadPointer (1) == testBuffers.get (8)); - expect (remapped.getReadPointer (2) == testBuffers.get (9)); - expect (remapped.getReadPointer (3) == testBuffers.get (10)); - expect (remapped.getReadPointer (4) == testBuffers.get (11)); - expect (remapped.getReadPointer (5) == testBuffers.get (12)); - expect (remapped.getReadPointer (6) == testBuffers.get (15)); // JUCE surround side -> VST3 surround side - expect (remapped.getReadPointer (7) == testBuffers.get (16)); // JUCE surround side -> VST3 surround side - expect (remapped.getReadPointer (8) == testBuffers.get (13)); // JUCE surround rear -> VST3 surround rear - expect (remapped.getReadPointer (9) == testBuffers.get (14)); // JUCE surround rear -> VST3 surround rear + + { + ClientRemappedBuffer scopedBuffer { remapper, &config.ins, &config.outs, data }; + auto& remapped = scopedBuffer.buffer; + + expect (remapped.getNumChannels() == 10); + + // Data from the input channels is copied to the correct channels of the remapped buffer + expect (allMatch (remapped, 0, 1.0f)); + expect (allMatch (remapped, 1, 2.0f)); + expect (allMatch (remapped, 2, 3.0f)); + expect (allMatch (remapped, 3, 4.0f)); + expect (allMatch (remapped, 4, 5.0f)); + expect (allMatch (remapped, 5, 6.0f)); + expect (allMatch (remapped, 6, 7.0f)); + // The remaining channels are output-only, so they may contain any data + + // Write some data to the buffer in JUCE layout + for (auto i = 0; i < remapped.getNumChannels(); ++i) + { + auto* ptr = remapped.getWritePointer (i); + std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); + } + } + + // Channels are copied back to the correct output buffer + expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); + expect (channelStartsWithValue (data.outputs[0], 1, 1.0f)); + + expect (channelStartsWithValue (data.outputs[1], 0, 2.0f)); + expect (channelStartsWithValue (data.outputs[1], 1, 3.0f)); + expect (channelStartsWithValue (data.outputs[1], 2, 4.0f)); + expect (channelStartsWithValue (data.outputs[1], 3, 5.0f)); + expect (channelStartsWithValue (data.outputs[1], 4, 8.0f)); // JUCE surround side -> VST3 surround side + expect (channelStartsWithValue (data.outputs[1], 5, 9.0f)); + expect (channelStartsWithValue (data.outputs[1], 6, 6.0f)); // JUCE surround rear -> VST3 surround rear + expect (channelStartsWithValue (data.outputs[1], 7, 7.0f)); } - beginTest ("A layout with more input channels than output channels uses input channels directly in remapped buffer"); + beginTest ("A layout with more input channels than output channels doesn't attempt to output any input channels"); { ClientBufferMapperData remapper; remapper.prepare (15, blockSize * 2); - const Config config { { ChannelMapping { AudioChannelSet::create7point1point6() }, - ChannelMapping { AudioChannelSet::mono() } }, - { ChannelMapping { AudioChannelSet::createLCRS() }, - ChannelMapping { AudioChannelSet::stereo() } } }; + const Config config { { DynamicChannelMapping { AudioChannelSet::create7point1point6() }, + DynamicChannelMapping { AudioChannelSet::mono() } }, + { DynamicChannelMapping { AudioChannelSet::createLCRS() }, + DynamicChannelMapping { AudioChannelSet::stereo() } } }; TestBuffers testBuffers { blockSize }; @@ -276,116 +304,190 @@ public: auto data = makeProcessData (blockSize, ins, outs); testBuffers.init(); - const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); - - expect (remapped.getNumChannels() == 15); - - // Data from the input channels is copied to the correct channels of the remapped buffer - expect (allMatch (remapped, 0, 1.0f)); - expect (allMatch (remapped, 1, 2.0f)); - expect (allMatch (remapped, 2, 3.0f)); - expect (allMatch (remapped, 3, 4.0f)); - expect (allMatch (remapped, 4, 7.0f)); - expect (allMatch (remapped, 5, 8.0f)); - expect (allMatch (remapped, 6, 9.0f)); - expect (allMatch (remapped, 7, 10.0f)); - expect (allMatch (remapped, 8, 11.0f)); - expect (allMatch (remapped, 9, 12.0f)); - expect (allMatch (remapped, 10, 5.0f)); - expect (allMatch (remapped, 11, 6.0f)); - expect (allMatch (remapped, 12, 13.0f)); - expect (allMatch (remapped, 13, 14.0f)); - expect (allMatch (remapped, 14, 15.0f)); - - // Use output channel pointers for output channels - expect (remapped.getReadPointer (0) == testBuffers.get (15)); - expect (remapped.getReadPointer (1) == testBuffers.get (16)); - expect (remapped.getReadPointer (2) == testBuffers.get (17)); - expect (remapped.getReadPointer (3) == testBuffers.get (18)); - expect (remapped.getReadPointer (4) == testBuffers.get (19)); - expect (remapped.getReadPointer (5) == testBuffers.get (20)); - - // Use input channel pointers for channels with no corresponding output - expect (remapped.getReadPointer (6) == testBuffers.get (8)); - expect (remapped.getReadPointer (7) == testBuffers.get (9)); - expect (remapped.getReadPointer (8) == testBuffers.get (10)); - expect (remapped.getReadPointer (9) == testBuffers.get (11)); - expect (remapped.getReadPointer (10) == testBuffers.get (4)); - expect (remapped.getReadPointer (11) == testBuffers.get (5)); - expect (remapped.getReadPointer (12) == testBuffers.get (12)); - expect (remapped.getReadPointer (13) == testBuffers.get (13)); - expect (remapped.getReadPointer (14) == testBuffers.get (14)); + + { + ClientRemappedBuffer scopedBuffer { remapper, &config.ins, &config.outs, data }; + auto& remapped = scopedBuffer.buffer; + + expect (remapped.getNumChannels() == 15); + + // Data from the input channels is copied to the correct channels of the remapped buffer + expect (allMatch (remapped, 0, 1.0f)); + expect (allMatch (remapped, 1, 2.0f)); + expect (allMatch (remapped, 2, 3.0f)); + expect (allMatch (remapped, 3, 4.0f)); + expect (allMatch (remapped, 4, 7.0f)); + expect (allMatch (remapped, 5, 8.0f)); + expect (allMatch (remapped, 6, 9.0f)); + expect (allMatch (remapped, 7, 10.0f)); + expect (allMatch (remapped, 8, 11.0f)); + expect (allMatch (remapped, 9, 12.0f)); + expect (allMatch (remapped, 10, 5.0f)); + expect (allMatch (remapped, 11, 6.0f)); + expect (allMatch (remapped, 12, 13.0f)); + expect (allMatch (remapped, 13, 14.0f)); + expect (allMatch (remapped, 14, 15.0f)); + + // Write some data to the buffer in JUCE layout + for (auto i = 0; i < remapped.getNumChannels(); ++i) + { + auto* ptr = remapped.getWritePointer (i); + std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); + } + } + + // Channels are copied back to the correct output buffer + expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); + expect (channelStartsWithValue (data.outputs[0], 1, 1.0f)); + expect (channelStartsWithValue (data.outputs[0], 2, 2.0f)); + expect (channelStartsWithValue (data.outputs[0], 3, 3.0f)); + + expect (channelStartsWithValue (data.outputs[1], 0, 4.0f)); + expect (channelStartsWithValue (data.outputs[1], 1, 5.0f)); } beginTest ("Inactive buses are ignored"); { ClientBufferMapperData remapper; - remapper.prepare (15, blockSize * 2); + remapper.prepare (18, blockSize * 2); - const Config config { { ChannelMapping { AudioChannelSet::create7point1point6() }, - ChannelMapping { AudioChannelSet::mono(), false }, - ChannelMapping { AudioChannelSet::quadraphonic() }, - ChannelMapping { AudioChannelSet::mono(), false } }, - { ChannelMapping { AudioChannelSet::create5point0(), false }, - ChannelMapping { AudioChannelSet::createLCRS() }, - ChannelMapping { AudioChannelSet::stereo() } } }; + Config config { { DynamicChannelMapping { AudioChannelSet::create7point1point6() }, + DynamicChannelMapping { AudioChannelSet::mono(), false }, + DynamicChannelMapping { AudioChannelSet::quadraphonic() }, + DynamicChannelMapping { AudioChannelSet::mono(), false } }, + { DynamicChannelMapping { AudioChannelSet::create5point0(), false }, + DynamicChannelMapping { AudioChannelSet::createLCRS() }, + DynamicChannelMapping { AudioChannelSet::stereo() } } }; + + config.ins[1].setHostActive (false); + config.ins[3].setHostActive (false); TestBuffers testBuffers { blockSize }; - // The host doesn't need to provide trailing buses that are inactive + // The host doesn't need to provide trailing buses that are inactive, as long as the + // client knows those buses are inactive. auto ins = MultiBusBuffers{}.withBus (testBuffers, 14).withBus (testBuffers, 1).withBus (testBuffers, 4); auto outs = MultiBusBuffers{}.withBus (testBuffers, 5) .withBus (testBuffers, 4).withBus (testBuffers, 2); auto data = makeProcessData (blockSize, ins, outs); testBuffers.init(); - const auto remapped = remapper.getMappedBuffer (data, config.ins, config.outs); - - expect (remapped.getNumChannels() == 18); - - // Data from the input channels is copied to the correct channels of the remapped buffer - expect (allMatch (remapped, 0, 1.0f)); - expect (allMatch (remapped, 1, 2.0f)); - expect (allMatch (remapped, 2, 3.0f)); - expect (allMatch (remapped, 3, 4.0f)); - expect (allMatch (remapped, 4, 7.0f)); - expect (allMatch (remapped, 5, 8.0f)); - expect (allMatch (remapped, 6, 9.0f)); - expect (allMatch (remapped, 7, 10.0f)); - expect (allMatch (remapped, 8, 11.0f)); - expect (allMatch (remapped, 9, 12.0f)); - expect (allMatch (remapped, 10, 5.0f)); - expect (allMatch (remapped, 11, 6.0f)); - expect (allMatch (remapped, 12, 13.0f)); - expect (allMatch (remapped, 13, 14.0f)); - - expect (allMatch (remapped, 14, 16.0f)); - expect (allMatch (remapped, 15, 17.0f)); - expect (allMatch (remapped, 16, 18.0f)); - expect (allMatch (remapped, 17, 19.0f)); - - // Use output channel pointers for output channels - expect (remapped.getReadPointer (0) == testBuffers.get (24)); - expect (remapped.getReadPointer (1) == testBuffers.get (25)); - expect (remapped.getReadPointer (2) == testBuffers.get (26)); - expect (remapped.getReadPointer (3) == testBuffers.get (27)); - expect (remapped.getReadPointer (4) == testBuffers.get (28)); - expect (remapped.getReadPointer (5) == testBuffers.get (29)); - - // Use input channel pointers for channels with no corresponding output - expect (remapped.getReadPointer (6) == testBuffers.get (8)); - expect (remapped.getReadPointer (7) == testBuffers.get (9)); - expect (remapped.getReadPointer (8) == testBuffers.get (10)); - expect (remapped.getReadPointer (9) == testBuffers.get (11)); - expect (remapped.getReadPointer (10) == testBuffers.get (4)); - expect (remapped.getReadPointer (11) == testBuffers.get (5)); - expect (remapped.getReadPointer (12) == testBuffers.get (12)); - expect (remapped.getReadPointer (13) == testBuffers.get (13)); - - expect (remapped.getReadPointer (14) == testBuffers.get (15)); - expect (remapped.getReadPointer (15) == testBuffers.get (16)); - expect (remapped.getReadPointer (16) == testBuffers.get (17)); - expect (remapped.getReadPointer (17) == testBuffers.get (18)); + + { + ClientRemappedBuffer scopedBuffer { remapper, &config.ins, &config.outs, data }; + auto& remapped = scopedBuffer.buffer; + + expect (remapped.getNumChannels() == 18); + + // Data from the input channels is copied to the correct channels of the remapped buffer + expect (allMatch (remapped, 0, 1.0f)); + expect (allMatch (remapped, 1, 2.0f)); + expect (allMatch (remapped, 2, 3.0f)); + expect (allMatch (remapped, 3, 4.0f)); + expect (allMatch (remapped, 4, 7.0f)); + expect (allMatch (remapped, 5, 8.0f)); + expect (allMatch (remapped, 6, 9.0f)); + expect (allMatch (remapped, 7, 10.0f)); + expect (allMatch (remapped, 8, 11.0f)); + expect (allMatch (remapped, 9, 12.0f)); + expect (allMatch (remapped, 10, 5.0f)); + expect (allMatch (remapped, 11, 6.0f)); + expect (allMatch (remapped, 12, 13.0f)); + expect (allMatch (remapped, 13, 14.0f)); + + expect (allMatch (remapped, 14, 16.0f)); + expect (allMatch (remapped, 15, 17.0f)); + expect (allMatch (remapped, 16, 18.0f)); + expect (allMatch (remapped, 17, 19.0f)); + + // Write some data to the buffer in JUCE layout + for (auto i = 0; i < remapped.getNumChannels(); ++i) + { + auto* ptr = remapped.getWritePointer (i); + std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); + } + } + + // All channels on the first output bus should be cleared, because the plugin + // thinks that this bus is inactive. + expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); + expect (channelStartsWithValue (data.outputs[0], 1, 0.0f)); + expect (channelStartsWithValue (data.outputs[0], 2, 0.0f)); + expect (channelStartsWithValue (data.outputs[0], 3, 0.0f)); + expect (channelStartsWithValue (data.outputs[0], 4, 0.0f)); + + // Remaining channels should be copied back as normal + expect (channelStartsWithValue (data.outputs[1], 0, 0.0f)); + expect (channelStartsWithValue (data.outputs[1], 1, 1.0f)); + expect (channelStartsWithValue (data.outputs[1], 2, 2.0f)); + expect (channelStartsWithValue (data.outputs[1], 3, 3.0f)); + + expect (channelStartsWithValue (data.outputs[2], 0, 4.0f)); + expect (channelStartsWithValue (data.outputs[2], 1, 5.0f)); + } + + beginTest ("Null pointers are allowed on inactive buses provided to clients"); + { + ClientBufferMapperData remapper; + remapper.prepare (8, blockSize * 2); + + const std::vector emptyBuses; + const std::vector stereoBus { ChannelMapping { AudioChannelSet::stereo() } }; + + Config config { { DynamicChannelMapping { AudioChannelSet::stereo() }, + DynamicChannelMapping { AudioChannelSet::quadraphonic(), false }, + DynamicChannelMapping { AudioChannelSet::stereo() } }, + { DynamicChannelMapping { AudioChannelSet::quadraphonic() }, + DynamicChannelMapping { AudioChannelSet::stereo(), false }, + DynamicChannelMapping { AudioChannelSet::quadraphonic() } } }; + + config.ins[1].setHostActive (false); + config.outs[1].setHostActive (false); + + TestBuffers testBuffers { blockSize }; + + auto ins = MultiBusBuffers{}.withBus (testBuffers, 2).withBus (testBuffers, 4).withBus (testBuffers, 2); + auto outs = MultiBusBuffers{}.withBus (testBuffers, 4).withBus (testBuffers, 2).withBus (testBuffers, 4); + + auto data = makeProcessData (blockSize, ins, outs); + + for (auto i = 0; i < 4; ++i) + data.inputs [1].channelBuffers32[i] = nullptr; + + for (auto i = 0; i < 2; ++i) + data.outputs[1].channelBuffers32[i] = nullptr; + + testBuffers.init(); + + { + ClientRemappedBuffer scopedBuffer { remapper, &config.ins, &config.outs, data }; + auto& remapped = scopedBuffer.buffer; + + expect (remapped.getNumChannels() == 8); + + expect (allMatch (remapped, 0, 1.0f)); + expect (allMatch (remapped, 1, 2.0f)); + // skip 4 inactive channels + expect (allMatch (remapped, 2, 7.0f)); + expect (allMatch (remapped, 3, 8.0f)); + + // Write some data to the buffer in JUCE layout + for (auto i = 0; i < remapped.getNumChannels(); ++i) + { + auto* ptr = remapped.getWritePointer (i); + std::fill (ptr, ptr + remapped.getNumSamples(), (float) i); + } + } + + expect (channelStartsWithValue (data.outputs[0], 0, 0.0f)); + expect (channelStartsWithValue (data.outputs[0], 1, 1.0f)); + expect (channelStartsWithValue (data.outputs[0], 2, 2.0f)); + expect (channelStartsWithValue (data.outputs[0], 3, 3.0f)); + + expect (channelStartsWithValue (data.outputs[2], 0, 4.0f)); + expect (channelStartsWithValue (data.outputs[2], 1, 5.0f)); + expect (channelStartsWithValue (data.outputs[2], 2, 6.0f)); + expect (channelStartsWithValue (data.outputs[2], 3, 7.0f)); } beginTest ("HostBufferMapper reorders channels correctly"); @@ -454,9 +556,17 @@ private: //============================================================================== struct Config { - std::vector ins, outs; + Config (std::vector i, std::vector o) + : ins (std::move (i)), outs (std::move (o)) + { + for (auto container : { &ins, &outs }) + for (auto& x : *container) + x.setHostActive (true); + } + + std::vector ins, outs; - int getNumChannels() const { return countUsedChannels (ins, outs); } + int getNumChannels() const { return countUsedClientChannels (ins, outs); } }; struct TestBuffers @@ -495,6 +605,11 @@ private: int numSamples = 0; }; + static bool channelStartsWithValue (Steinberg::Vst::AudioBusBuffers& bus, size_t index, float value) + { + return bus.channelBuffers32[index][0] == value; + } + static bool allMatch (const AudioBuffer& buf, int index, float value) { const auto* ptr = buf.getReadPointer (index); diff --git a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp index ca59063a2..7336fff7d 100644 --- a/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp +++ b/source/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp @@ -71,7 +71,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4355) #endif #ifndef JUCE_VST_FALLBACK_HOST_NAME - #define JUCE_VST_FALLBACK_HOST_NAME "Carla Plugin Host" + #define JUCE_VST_FALLBACK_HOST_NAME "Juce VST Host" #endif //============================================================================== @@ -2120,7 +2120,7 @@ private: if (effect != nullptr && effect->interfaceIdentifier == Vst2::juceVstInterfaceIdentifier) { jassert (effect->hostSpace2 == 0); - jassert (effect->effectPointer != 0); + jassert (effect->effectPointer != nullptr); _fpreset(); // some dodgy plugs mess around with this } @@ -2274,6 +2274,20 @@ private: return { nullptr, nullptr }; } + template + void setFromOptional (Member& target, Optional opt, int32_t flag) + { + if (opt.hasValue()) + { + target = static_cast (*opt); + vstHostTime.flags |= flag; + } + else + { + vstHostTime.flags &= ~flag; + } + } + //============================================================================== template void processAudio (AudioBuffer& buffer, MidiBuffer& midiMessages, @@ -2299,34 +2313,32 @@ private: { if (auto* currentPlayHead = getPlayHead()) { - AudioPlayHead::CurrentPositionInfo position; - - if (currentPlayHead->getCurrentPosition (position)) + if (const auto position = currentPlayHead->getPosition()) { - vstHostTime.samplePosition = (double) position.timeInSamples; - vstHostTime.tempoBPM = position.bpm; - vstHostTime.timeSignatureNumerator = position.timeSigNumerator; - vstHostTime.timeSignatureDenominator = position.timeSigDenominator; - vstHostTime.musicalPosition = position.ppqPosition; - vstHostTime.lastBarPosition = position.ppqPositionOfLastBarStart; - vstHostTime.flags |= Vst2::vstTimingInfoFlagTempoValid - | Vst2::vstTimingInfoFlagTimeSignatureValid - | Vst2::vstTimingInfoFlagMusicalPositionValid - | Vst2::vstTimingInfoFlagLastBarPositionValid; - - if (const auto* hostTimeNs = getHostTimeNs()) + if (const auto samplePos = position->getTimeInSamples()) + vstHostTime.samplePosition = (double) *samplePos; + else + jassertfalse; // VST hosts *must* call setTimeInSamples on the audio playhead + + if (auto sig = position->getTimeSignature()) { - vstHostTime.systemTimeNanoseconds = (double) *hostTimeNs; - vstHostTime.flags |= Vst2::vstTimingInfoFlagNanosecondsValid; + vstHostTime.flags |= Vst2::vstTimingInfoFlagTimeSignatureValid; + vstHostTime.timeSignatureNumerator = sig->numerator; + vstHostTime.timeSignatureDenominator = sig->denominator; } else { - vstHostTime.flags &= ~Vst2::vstTimingInfoFlagNanosecondsValid; + vstHostTime.flags &= ~Vst2::vstTimingInfoFlagTimeSignatureValid; } + setFromOptional (vstHostTime.musicalPosition, position->getPpqPosition(), Vst2::vstTimingInfoFlagMusicalPositionValid); + setFromOptional (vstHostTime.lastBarPosition, position->getPpqPositionOfLastBarStart(), Vst2::vstTimingInfoFlagLastBarPositionValid); + setFromOptional (vstHostTime.systemTimeNanoseconds, position->getHostTimeNs(), Vst2::vstTimingInfoFlagNanosecondsValid); + setFromOptional (vstHostTime.tempoBPM, position->getBpm(), Vst2::vstTimingInfoFlagTempoValid); + int32 newTransportFlags = 0; - if (position.isPlaying) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyPlaying; - if (position.isRecording) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyRecording; + if (position->getIsPlaying()) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyPlaying; + if (position->getIsRecording()) newTransportFlags |= Vst2::vstTimingInfoFlagCurrentlyRecording; if (newTransportFlags != (vstHostTime.flags & (Vst2::vstTimingInfoFlagCurrentlyPlaying | Vst2::vstTimingInfoFlagCurrentlyRecording))) @@ -2334,15 +2346,18 @@ private: else vstHostTime.flags &= ~Vst2::vstTimingInfoFlagTransportChanged; - const auto optionalFrameRate = [&fr = position.frameRate]() -> Optional + const auto optionalFrameRate = [fr = position->getFrameRate()]() -> Optional { - switch (fr.getBaseRate()) + if (! fr.hasValue()) + return {}; + + switch (fr->getBaseRate()) { - case 24: return fr.isPullDown() ? Vst2::vstSmpteRateFps239 : Vst2::vstSmpteRateFps24; - case 25: return fr.isPullDown() ? Vst2::vstSmpteRateFps249 : Vst2::vstSmpteRateFps25; - case 30: return fr.isPullDown() ? (fr.isDrop() ? Vst2::vstSmpteRateFps2997drop : Vst2::vstSmpteRateFps2997) - : (fr.isDrop() ? Vst2::vstSmpteRateFps30drop : Vst2::vstSmpteRateFps30); - case 60: return fr.isPullDown() ? Vst2::vstSmpteRateFps599 : Vst2::vstSmpteRateFps60; + case 24: return fr->isPullDown() ? Vst2::vstSmpteRateFps239 : Vst2::vstSmpteRateFps24; + case 25: return fr->isPullDown() ? Vst2::vstSmpteRateFps249 : Vst2::vstSmpteRateFps25; + case 30: return fr->isPullDown() ? (fr->isDrop() ? Vst2::vstSmpteRateFps2997drop : Vst2::vstSmpteRateFps2997) + : (fr->isDrop() ? Vst2::vstSmpteRateFps30drop : Vst2::vstSmpteRateFps30); + case 60: return fr->isPullDown() ? Vst2::vstSmpteRateFps599 : Vst2::vstSmpteRateFps60; } return {}; @@ -2350,18 +2365,24 @@ private: vstHostTime.flags |= optionalFrameRate ? Vst2::vstTimingInfoFlagSmpteValid : 0; vstHostTime.smpteRate = optionalFrameRate.orFallback (0); - vstHostTime.smpteOffset = (int32) (position.timeInSeconds * 80.0 * position.frameRate.getEffectiveRate() + 0.5); + const auto effectiveRate = position->getFrameRate().hasValue() ? position->getFrameRate()->getEffectiveRate() : 0.0; + vstHostTime.smpteOffset = (int32) (position->getTimeInSeconds().orFallback (0.0) * 80.0 * effectiveRate + 0.5); - if (position.isLooping) + if (const auto loop = position->getLoopPoints()) { - vstHostTime.loopStartPosition = position.ppqLoopStart; - vstHostTime.loopEndPosition = position.ppqLoopEnd; - vstHostTime.flags |= (Vst2::vstTimingInfoFlagLoopPositionValid | Vst2::vstTimingInfoFlagLoopActive); + vstHostTime.flags |= Vst2::vstTimingInfoFlagLoopPositionValid; + vstHostTime.loopStartPosition = loop->ppqStart; + vstHostTime.loopEndPosition = loop->ppqEnd; } else { - vstHostTime.flags &= ~(Vst2::vstTimingInfoFlagLoopPositionValid | Vst2::vstTimingInfoFlagLoopActive); + vstHostTime.flags &= ~Vst2::vstTimingInfoFlagLoopPositionValid; } + + if (position->getIsLooping()) + vstHostTime.flags |= Vst2::vstTimingInfoFlagLoopActive; + else + vstHostTime.flags &= ~Vst2::vstTimingInfoFlagLoopActive; } } diff --git a/source/modules/juce_audio_processors/juce_audio_processors.cpp b/source/modules/juce_audio_processors/juce_audio_processors.cpp index 86ea97fac..e0b75a685 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.cpp +++ b/source/modules/juce_audio_processors/juce_audio_processors.cpp @@ -42,7 +42,6 @@ #include "juce_audio_processors.h" #include -#include //============================================================================== #if (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3) && (JUCE_LINUX || JUCE_BSD) && ! JUCE_AUDIOPROCESSOR_NO_GUI @@ -157,21 +156,19 @@ private: } } - struct FlippedNSView : public ObjCClass + struct InnerNSView : public ObjCClass { - FlippedNSView() - : ObjCClass ("JuceFlippedNSView_") + InnerNSView() + : ObjCClass ("JuceInnerNSView_") { addIvar ("owner"); - addMethod (@selector (isFlipped), isFlipped); addMethod (@selector (isOpaque), isOpaque); addMethod (@selector (didAddSubview:), didAddSubview); registerClass(); } - static BOOL isFlipped (id, SEL) { return YES; } static BOOL isOpaque (id, SEL) { return YES; } static void nudge (id self) @@ -181,15 +178,12 @@ private: owner->triggerAsyncUpdate(); } - static void viewDidUnhide (id self, SEL) { nudge (self); } static void didAddSubview (id self, SEL, NSView*) { nudge (self); } - static void viewDidMoveToSuperview (id self, SEL) { nudge (self); } - static void viewDidMoveToWindow (id self, SEL) { nudge (self); } }; - static FlippedNSView& getViewClass() + static InnerNSView& getViewClass() { - static FlippedNSView result; + static InnerNSView result; return result; } }; @@ -201,46 +195,51 @@ private: #include "utilities/juce_FlagCache.h" #include "format/juce_AudioPluginFormat.cpp" #include "format/juce_AudioPluginFormatManager.cpp" -// #include "format_types/juce_LegacyAudioParameter.cpp" +#include "format_types/juce_LegacyAudioParameter.cpp" #include "processors/juce_AudioProcessor.cpp" #include "processors/juce_AudioPluginInstance.cpp" -// #include "processors/juce_AudioProcessorGraph.cpp" +#include "processors/juce_AudioProcessorGraph.cpp" #if ! JUCE_AUDIOPROCESSOR_NO_GUI #include "processors/juce_AudioProcessorEditor.cpp" -// #include "processors/juce_GenericAudioProcessorEditor.cpp" + #include "processors/juce_GenericAudioProcessorEditor.cpp" #endif #include "processors/juce_PluginDescription.cpp" -// #include "format_types/juce_ARACommon.cpp" -// #include "format_types/juce_LADSPAPluginFormat.cpp" +#include "format_types/juce_ARACommon.cpp" +#include "format_types/juce_LADSPAPluginFormat.cpp" #include "format_types/juce_VSTPluginFormat.cpp" #include "format_types/juce_VST3PluginFormat.cpp" #include "format_types/juce_AudioUnitPluginFormat.mm" -// #include "format_types/juce_ARAHosting.cpp" +#include "format_types/juce_ARAHosting.cpp" #if ! JUCE_AUDIOPROCESSOR_NO_GUI #include "scanning/juce_KnownPluginList.cpp" -// #include "scanning/juce_PluginDirectoryScanner.cpp" -// #include "scanning/juce_PluginListComponent.cpp" + #include "scanning/juce_PluginDirectoryScanner.cpp" + #include "scanning/juce_PluginListComponent.cpp" #endif #include "processors/juce_AudioProcessorParameterGroup.cpp" #include "utilities/juce_AudioProcessorParameterWithID.cpp" -// #include "utilities/juce_RangedAudioParameter.cpp" -// #include "utilities/juce_AudioParameterFloat.cpp" -// #include "utilities/juce_AudioParameterInt.cpp" -// #include "utilities/juce_AudioParameterBool.cpp" -// #include "utilities/juce_AudioParameterChoice.cpp" -// #if ! JUCE_AUDIOPROCESSOR_NO_GUI -// #include "utilities/juce_ParameterAttachments.cpp" -// #endif -// #include "utilities/juce_AudioProcessorValueTreeState.cpp" -// #include "utilities/juce_PluginHostType.cpp" +#include "utilities/juce_RangedAudioParameter.cpp" +#include "utilities/juce_AudioParameterFloat.cpp" +#include "utilities/juce_AudioParameterInt.cpp" +#include "utilities/juce_AudioParameterBool.cpp" +#include "utilities/juce_AudioParameterChoice.cpp" +#if ! JUCE_AUDIOPROCESSOR_NO_GUI + #include "utilities/juce_ParameterAttachments.cpp" +#endif +#include "utilities/juce_AudioProcessorValueTreeState.cpp" +#include "utilities/juce_PluginHostType.cpp" #include "utilities/juce_NativeScaleFactorNotifier.cpp" -// #include "utilities/ARA/juce_ARA_utils.cpp" -// -// #include "format_types/juce_LV2PluginFormat.cpp" - +#include "utilities/ARA/juce_ARA_utils.cpp" + +#include "format_types/juce_LV2PluginFormat.cpp" + #if JUCE_UNIT_TESTS - #include "format_types/juce_VST3PluginFormat_test.cpp" - #include "format_types/juce_LV2PluginFormat_test.cpp" + #if JUCE_PLUGINHOST_VST3 + #include "format_types/juce_VST3PluginFormat_test.cpp" + #endif + + #if JUCE_PLUGINHOST_LV2 + #include "format_types/juce_LV2PluginFormat_test.cpp" + #endif #endif #if JUCE_AUDIOPROCESSOR_NO_GUI diff --git a/source/modules/juce_audio_processors/juce_audio_processors.h b/source/modules/juce_audio_processors/juce_audio_processors.h index e79553ec0..389caf794 100644 --- a/source/modules/juce_audio_processors/juce_audio_processors.h +++ b/source/modules/juce_audio_processors/juce_audio_processors.h @@ -35,7 +35,7 @@ ID: juce_audio_processors vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE audio processor classes description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. website: http://www.juce.com/juce diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h index 0743e4344..27b5eb9cd 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessor.h @@ -1141,11 +1141,6 @@ public: /** This method is called when the layout of the audio processor changes. */ virtual void processorLayoutsChanged(); - //============================================================================== - /** LV2 specific calls, saving/restore as string. */ - virtual String getStateInformationString () { return String(); } - virtual void setStateInformationString (const String&) {} - //============================================================================== /** Adds a listener that will be called when an aspect of this processor changes. */ virtual void addListener (AudioProcessorListener* newListener); @@ -1160,51 +1155,6 @@ public: */ virtual void setPlayHead (AudioPlayHead* newPlayHead); - //============================================================================== - /** Hosts may call this function to supply the system time corresponding to the - current audio buffer. - - If you want to set a valid time, pass a pointer to a uint64_t holding the current time. The - value will be copied into the AudioProcessor instance without any allocation/deallocation. - - If you want to clear any stored host time, pass nullptr. - - Calls to this function must be synchronised (i.e. not simultaneous) with the audio callback. - - @code - const auto currentHostTime = computeHostTimeNanos(); - processor.setHostTimeNanos (¤tHostTime); // Set a valid host time - // ...call processBlock etc. - processor.setHostTimeNanos (nullptr); // Clear host time - @endcode - */ - void setHostTimeNanos (const uint64_t* hostTimeIn) - { - hasHostTime = hostTimeIn != nullptr; - hostTime = hasHostTime ? *hostTimeIn : 0; - } - - /** The plugin may call this function inside the processBlock function (and only there!) - to find the timestamp associated with the current audio block. - - If a timestamp is available, this will return a pointer to that timestamp. You should - immediately copy the pointed-to value and use that in any following code. Do *not* free - any pointer returned by this function. - - If no timestamp is provided, this will return nullptr. - - @code - void processBlock (AudioBuffer&, MidiBuffer&) override - { - if (auto* timestamp = getHostTimeNs()) - { - // Use *timestamp here to compensate for callback jitter etc. - } - } - @endcode - */ - const uint64_t* getHostTimeNs() const { return hasHostTime ? &hostTime : nullptr; } - //============================================================================== /** This is called by the processor to specify its details before being played. Use this version of the function if you are not interested in any sidechain and/or aux buses @@ -1565,9 +1515,6 @@ private: AudioProcessorParameterGroup parameterTree; Array flatParameterList; - uint64_t hostTime = 0; - bool hasHostTime = false; - AudioProcessorParameter* getParamChecked (int) const; #if JUCE_DEBUG diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp index a66954944..cb6f1ce60 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.cpp @@ -46,9 +46,6 @@ AudioProcessorEditor::~AudioProcessorEditor() removeComponentListener (resizeListener.get()); } -// FIXME -void* AudioProcessorEditor::getPlatformSpecificData() { return nullptr; } - void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h index ba71788d8..af1805ac3 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorEditor.h @@ -50,9 +50,6 @@ protected: AudioProcessorEditor (AudioProcessor*) noexcept; public: - // FIXME - virtual void* getPlatformSpecificData(); - /** Destructor. */ ~AudioProcessorEditor() override; diff --git a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 456f5dfed..6a0bed1af 100644 --- a/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/source/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -37,18 +37,15 @@ static void updateOnMessageThread (AsyncUpdater& updater) template struct GraphRenderSequence { - GraphRenderSequence() {} - struct Context { FloatType** audioBuffers; MidiBuffer* midiBuffers; AudioPlayHead* audioPlayHead; - Optional hostTimeNs; int numSamples; }; - void perform (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead, Optional hostTimeNs) + void perform (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioPlayHead* audioPlayHead) { auto numSamples = buffer.getNumSamples(); auto maxSamples = renderingBuffer.getNumSamples(); @@ -67,7 +64,7 @@ struct GraphRenderSequence // Splitting up the buffer like this will cause the play head and host time to be // invalid for all but the first chunk... - perform (audioChunk, midiChunk, audioPlayHead, hostTimeNs); + perform (audioChunk, midiChunk, audioPlayHead); chunkStartSample += maxSamples; } @@ -82,7 +79,7 @@ struct GraphRenderSequence currentMidiOutputBuffer.clear(); { - const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, hostTimeNs, numSamples }; + const Context context { renderingBuffer.getArrayOfWritePointers(), midiBuffers.begin(), audioPlayHead, numSamples }; for (auto* op : renderOps) op->perform (context); @@ -267,7 +264,6 @@ private: void perform (const Context& c) override { processor.setPlayHead (c.audioPlayHead); - processor.setHostTimeNanos (c.hostTimeNs.hasValue() ? &(*c.hostTimeNs) : nullptr); for (int i = 0; i < totalChans; ++i) audioChannels[i] = c.audioBuffers[audioChannelsToUse.getUnchecked (i)]; @@ -289,8 +285,6 @@ private: buffer.clear(); else callProcess (buffer, c.midiBuffers[midiBufferToUse]); - - processor.setHostTimeNanos (nullptr); } void callProcess (AudioBuffer& buffer, MidiBuffer& midiMessages) @@ -1402,14 +1396,6 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m std::unique_ptr& renderSequence, std::atomic& isPrepared) { - const auto getHostTime = [&]() -> Optional - { - if (auto* nanos = graph.getHostTimeNs()) - return *nanos; - - return nullopt; - }; - if (graph.isNonRealtime()) { while (! isPrepared) @@ -1418,7 +1404,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m const ScopedLock sl (graph.getCallbackLock()); if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); } else { @@ -1427,7 +1413,7 @@ static void processBlockForBuffer (AudioBuffer& buffer, MidiBuffer& m if (isPrepared) { if (renderSequence != nullptr) - renderSequence->perform (buffer, midiMessages, graph.getPlayHead(), getHostTime()); + renderSequence->perform (buffer, midiMessages, graph.getPlayHead()); } else { diff --git a/source/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h b/source/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h index 2787972c9..b06a83122 100644 --- a/source/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h +++ b/source/modules/juce_audio_processors/utilities/ARA/juce_ARADocumentController.h @@ -335,28 +335,28 @@ protected: //============================================================================== /** Override to return a custom subclass instance of ARADocument. */ - ARADocument* doCreateDocument(); + virtual ARADocument* doCreateDocument(); /** Override to return a custom subclass instance of ARAMusicalContext. */ - ARAMusicalContext* doCreateMusicalContext (ARADocument* document, - ARA::ARAMusicalContextHostRef hostRef); + virtual ARAMusicalContext* doCreateMusicalContext (ARADocument* document, + ARA::ARAMusicalContextHostRef hostRef); /** Override to return a custom subclass instance of ARARegionSequence. */ - ARARegionSequence* doCreateRegionSequence (ARADocument* document, - ARA::ARARegionSequenceHostRef hostRef); + virtual ARARegionSequence* doCreateRegionSequence (ARADocument* document, + ARA::ARARegionSequenceHostRef hostRef); /** Override to return a custom subclass instance of ARAAudioSource. */ - ARAAudioSource* doCreateAudioSource (ARADocument* document, - ARA::ARAAudioSourceHostRef hostRef); + virtual ARAAudioSource* doCreateAudioSource (ARADocument* document, + ARA::ARAAudioSourceHostRef hostRef); /** Override to return a custom subclass instance of ARAAudioModification. */ - ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource, - ARA::ARAAudioModificationHostRef hostRef, - const ARAAudioModification* optionalModificationToClone); + virtual ARAAudioModification* doCreateAudioModification (ARAAudioSource* audioSource, + ARA::ARAAudioModificationHostRef hostRef, + const ARAAudioModification* optionalModificationToClone); /** Override to return a custom subclass instance of ARAPlaybackRegion. */ - ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification, - ARA::ARAPlaybackRegionHostRef hostRef); + virtual ARAPlaybackRegion* doCreatePlaybackRegion (ARAAudioModification* modification, + ARA::ARAPlaybackRegionHostRef hostRef); private: //============================================================================== diff --git a/source/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h b/source/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h index 021d25038..e5434e3c7 100644 --- a/source/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h +++ b/source/modules/juce_audio_processors/utilities/ARA/juce_ARAModelObjects.h @@ -458,7 +458,7 @@ public: } /** Called before the musical context is destroyed. - @param musicalContext The musical context that will be destoyed. + @param musicalContext The musical context that will be destroyed. */ virtual void willDestroyMusicalContext (ARAMusicalContext* musicalContext) { @@ -559,7 +559,7 @@ public: } /** Called before the playback region is destroyed. - @param playbackRegion The playback region that will be destoyed. + @param playbackRegion The playback region that will be destroyed. */ virtual void willDestroyPlaybackRegion (ARAPlaybackRegion* playbackRegion) { @@ -706,7 +706,7 @@ public: } /** Called before the region sequence is destroyed. - @param regionSequence The region sequence that will be destoyed. + @param regionSequence The region sequence that will be destroyed. */ virtual void willDestroyRegionSequence (ARARegionSequence* regionSequence) { @@ -898,7 +898,7 @@ public: } /** Called before the audio source is destroyed. - @param audioSource The audio source that will be destoyed. + @param audioSource The audio source that will be destroyed. */ virtual void willDestroyAudioSource (ARAAudioSource* audioSource) { @@ -1072,7 +1072,7 @@ public: } /** Called before the audio modification is destroyed. - @param audioModification The audio modification that will be destoyed. + @param audioModification The audio modification that will be destroyed. */ virtual void willDestroyAudioModification (ARAAudioModification* audioModification) { diff --git a/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp b/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp index f5535412d..fcece498b 100644 --- a/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp +++ b/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.cpp @@ -30,7 +30,7 @@ namespace juce bool ARARenderer::processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept + const AudioPlayHead::PositionInfo& positionInfo) noexcept { ignoreUnused (buffer, realtime, positionInfo); diff --git a/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h b/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h index c03fb4ee8..01762924e 100644 --- a/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h +++ b/source/modules/juce_audio_processors/utilities/ARA/juce_ARAPlugInInstanceRoles.h @@ -88,7 +88,7 @@ public: */ virtual bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept = 0; + const AudioPlayHead::PositionInfo& positionInfo) noexcept = 0; /** Renders the output into the given buffer. Returns true if rendering executed without error, false otherwise. @@ -108,7 +108,7 @@ public: */ virtual bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept; + const AudioPlayHead::PositionInfo& positionInfo) noexcept; }; //============================================================================== @@ -128,7 +128,7 @@ public: bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + const AudioPlayHead::PositionInfo& positionInfo) noexcept override { ignoreUnused (buffer, realtime, positionInfo); return false; @@ -189,7 +189,7 @@ public: // isNonRealtime of the process context - typically preview is limited to realtime. bool processBlock (AudioBuffer& buffer, AudioProcessor::Realtime isNonRealtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) noexcept override + const AudioPlayHead::PositionInfo& positionInfo) noexcept override { ignoreUnused (buffer, isNonRealtime, positionInfo); return true; diff --git a/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp b/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp index a8471b9cf..9d871b3d7 100644 --- a/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp +++ b/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.cpp @@ -84,7 +84,7 @@ bool AudioProcessorARAExtension::releaseResourcesForARA() bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo) + const AudioPlayHead::PositionInfo& positionInfo) { // validate that the host has prepared us before processing ARA_VALIDATE_API_STATE (isPrepared); @@ -109,12 +109,10 @@ bool AudioProcessorARAExtension::processBlockForARA (AudioBuffer& buffer, juce::AudioProcessor::Realtime realtime, AudioPlayHead* playhead) { - AudioPlayHead::CurrentPositionInfo positionInfo; - - if (! isBoundToARA() || ! playhead || ! playhead->getCurrentPosition (positionInfo)) - positionInfo.resetToDefault(); - - return processBlockForARA (buffer, realtime, positionInfo); + return processBlockForARA (buffer, + realtime, + playhead != nullptr ? playhead->getPosition().orFallback (AudioPlayHead::PositionInfo{}) + : AudioPlayHead::PositionInfo{}); } //============================================================================== diff --git a/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h b/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h index c3669e29f..82a5644c4 100644 --- a/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h +++ b/source/modules/juce_audio_processors/utilities/ARA/juce_AudioProcessor_ARAExtensions.h @@ -144,7 +144,7 @@ protected: */ bool processBlockForARA (AudioBuffer& buffer, AudioProcessor::Realtime realtime, - const AudioPlayHead::CurrentPositionInfo& positionInfo); + const AudioPlayHead::PositionInfo& positionInfo); /** Implementation helper for AudioProcessor::processBlock(). diff --git a/source/modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h b/source/modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h index 6bfdd5059..f497fef96 100644 --- a/source/modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h +++ b/source/modules/juce_audio_processors/utilities/juce_ExtensionsVisitor.h @@ -122,6 +122,14 @@ struct ExtensionsVisitor virtual void createARAFactoryAsync (std::function) const = 0; }; + ExtensionsVisitor() = default; + + ExtensionsVisitor (const ExtensionsVisitor&) = default; + ExtensionsVisitor (ExtensionsVisitor&&) = default; + + ExtensionsVisitor& operator= (const ExtensionsVisitor&) = default; + ExtensionsVisitor& operator= (ExtensionsVisitor&&) = default; + virtual ~ExtensionsVisitor() = default; /** Will be called if there is no platform specific information available. */ diff --git a/source/modules/juce_core/containers/juce_Optional.h b/source/modules/juce_core/containers/juce_Optional.h index f7539fb05..6e2333ce9 100644 --- a/source/modules/juce_core/containers/juce_Optional.h +++ b/source/modules/juce_core/containers/juce_Optional.h @@ -20,10 +20,6 @@ ============================================================================== */ -#pragma once - -#include - namespace juce { @@ -38,24 +34,40 @@ constexpr auto isNothrowSwappable = noexcept (swap (std::declval(), std::dec } // namespace adlSwap } // namespace detail -struct Nullopt {}; +/** A type representing the null state of an Optional. + Similar to std::nullopt_t. +*/ +struct Nullopt +{ + explicit constexpr Nullopt (int) {} +}; -constexpr Nullopt nullopt; +/** An object that can be used when constructing and comparing Optional instances. + Similar to std::nullopt. +*/ +constexpr Nullopt nullopt { 0 }; // Without this, our tests can emit "unreachable code" warnings during // link time code generation. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4702) -/* For internal use only! - +/** A simple optional type. Has similar (not necessarily identical!) semantics to std::optional. + This is intended to stand-in for std::optional while JUCE's minimum + supported language standard is lower than C++17. When the minimum language + standard moves to C++17, this class will probably be deprecated, in much + the same way that juce::ScopedPointer was deprecated in favour of + std::unique_ptr after C++11. + This isn't really intended to be used by JUCE clients. Instead, it's to be used internally in JUCE code, with an API close-enough to std::optional that the types can be swapped with fairly minor disruption at some point in the future, but *without breaking any public APIs*. + + @tags{Core} */ template class Optional @@ -97,26 +109,26 @@ class Optional && NotConstructibleFromSimilarType::value>; public: - Optional() = default; + Optional() : placeholder() {} - Optional (Nullopt) noexcept {} + Optional (Nullopt) noexcept : placeholder() {} template ::value && ! std::is_same, Optional>::value>> Optional (U&& value) noexcept (noexcept (Value (std::forward (value)))) - : valid (true) + : storage (std::forward (value)), valid (true) { - new (&storage) Value (std::forward (value)); } Optional (Optional&& other) noexcept (noexcept (std::declval().constructFrom (other))) + : placeholder() { constructFrom (other); } Optional (const Optional& other) - : valid (other.valid) + : placeholder(), valid (other.valid) { if (valid) new (&storage) Value (*other); @@ -124,13 +136,14 @@ public: template > Optional (Optional&& other) noexcept (noexcept (std::declval().constructFrom (other))) + : placeholder() { constructFrom (other); } template > Optional (const Optional& other) - : valid (other.hasValue()) + : placeholder(), valid (other.hasValue()) { if (valid) new (&storage) Value (*other); @@ -167,7 +180,7 @@ public: return *this; } - /* Maintains the strong exception safety guarantee. */ + /** Maintains the strong exception safety guarantee. */ Optional& operator= (const Optional& other) { auto copy = other; @@ -182,7 +195,7 @@ public: return *this; } - /* Maintains the strong exception safety guarantee. */ + /** Maintains the strong exception safety guarantee. */ template > Optional& operator= (const Optional& other) { @@ -211,7 +224,7 @@ public: operator*().~Value(); } - /* Like std::optional::value_or */ + /** Like std::optional::value_or */ template Value orFallback (U&& fallback) const { return *this ? **this : std::forward (fallback); } @@ -271,12 +284,22 @@ private: } } - std::aligned_storage_t storage; + union + { + char placeholder; + Value storage; + }; bool valid = false; }; JUCE_END_IGNORE_WARNINGS_MSVC +template +Optional> makeOptional (Value&& v) +{ + return std::forward (v); +} + template bool operator== (const Optional& lhs, const Optional& rhs) { diff --git a/source/modules/juce_core/files/juce_AndroidDocument.h b/source/modules/juce_core/files/juce_AndroidDocument.h new file mode 100644 index 000000000..883ca7f2e --- /dev/null +++ b/source/modules/juce_core/files/juce_AndroidDocument.h @@ -0,0 +1,476 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2020 - Raw Material Software Limited + + 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 +{ + +//============================================================================== +/** + Some information about a document. + + Each instance represents some information about the document at the point when the instance + was created. + + Instance information is not updated automatically. If you think some file information may + have changed, create a new instance. + + @tags{Core} +*/ +class AndroidDocumentInfo +{ +public: + AndroidDocumentInfo() = default; + + /** True if this file really exists. */ + bool exists() const { return isJuceFlagSet (flagExists); } + + /** True if this is a directory rather than a file. */ + bool isDirectory() const; + + /** True if this is a file rather than a directory. */ + bool isFile() const { return type.isNotEmpty() && ! isDirectory(); } + + /** True if this process has permission to read this file. + + If this returns true, and the AndroidDocument refers to a file rather than a directory, + then AndroidDocument::createInputStream should work on this document. + */ + bool canRead() const { return isJuceFlagSet (flagHasReadPermission) && type.isNotEmpty(); } + + /** True if this is a document that can be written, or a directory that can be modified. + + If this returns true, and the AndroidDocument refers to a file rather than a directory, + then AndroidDocument::createOutputStream should work on this document. + */ + bool canWrite() const + { + return isJuceFlagSet (flagHasWritePermission) + && type.isNotEmpty() + && (isNativeFlagSet (flagSupportsWrite) + || isNativeFlagSet (flagSupportsDelete) + || isNativeFlagSet (flagDirSupportsCreate)); + } + + /** True if this document can be removed completely from the filesystem. */ + bool canDelete() const { return isNativeFlagSet (flagSupportsDelete); } + + /** True if this is a directory and adding child documents is supported. */ + bool canCreateChildren() const { return isNativeFlagSet (flagDirSupportsCreate); } + + /** True if this document can be renamed. */ + bool canRename() const { return isNativeFlagSet (flagSupportsRename); } + + /** True if this document can be copied. */ + bool canCopy() const { return isNativeFlagSet (flagSupportsCopy); } + + /** True if this document can be moved. */ + bool canMove() const { return isNativeFlagSet (flagSupportsMove); } + + /** True if this document isn't a physical file on storage. */ + bool isVirtual() const { return isNativeFlagSet (flagVirtualDocument); } + + /** The user-facing name. + + This may or may not contain a file extension. For files identified by a URL, the MIME type + is stored separately. + */ + String getName() const { return name; } + + /** The MIME type of this document. */ + String getType() const { return isDirectory() ? String{} : type; } + + /** Timestamp when a document was last modified, in milliseconds since January 1, 1970 00:00:00.0 UTC. + + Use isLastModifiedValid() to determine whether or not the result of this + function is valid. + */ + int64 getLastModified() const { return isJuceFlagSet (flagValidModified) ? lastModified : 0; } + + /** True if the filesystem provided a modification time. */ + bool isLastModifiedValid() const { return isJuceFlagSet (flagValidModified); } + + /** The size of the document in bytes, if known. + + Use isSizeInBytesValid() to determine whether or not the result of this + function is valid. + */ + int64 getSizeInBytes() const { return isJuceFlagSet (flagValidSize) ? sizeInBytes : 0; } + + /** True if the filesystem provided a size in bytes. */ + bool isSizeInBytesValid() const { return isJuceFlagSet (flagValidSize); } + + /** @internal */ + class Args; + +private: + explicit AndroidDocumentInfo (Args); + + bool isNativeFlagSet (int flag) const { return (nativeFlags & flag) != 0; } + bool isJuceFlagSet (int flag) const { return (juceFlags & flag) != 0; } + + /* Native Android flags that might be set in the COLUMN_FLAGS for a particular document */ + enum + { + flagSupportsWrite = 0x0002, + flagSupportsDelete = 0x0004, + flagDirSupportsCreate = 0x0008, + flagSupportsRename = 0x0040, + flagSupportsCopy = 0x0080, + flagSupportsMove = 0x0100, + flagVirtualDocument = 0x0200, + }; + + /* Flags for other binary properties that aren't exposed in COLUMN_FLAGS */ + enum + { + flagExists = 1 << 0, + flagValidModified = 1 << 1, + flagValidSize = 1 << 2, + flagHasReadPermission = 1 << 3, + flagHasWritePermission = 1 << 4, + }; + + String name; + String type; + int64 lastModified = 0; + int64 sizeInBytes = 0; + int nativeFlags = 0, juceFlags = 0; +}; + +//============================================================================== +/** + Represents a permission granted to an application to read and/or write to a particular document + or tree. + + This class also contains static methods to request, revoke, and query the permissions of your + app. These functions are no-ops on all platforms other than Android. + + @tags{Core} +*/ +class AndroidDocumentPermission +{ +public: + /** The url of the document with persisted permissions. */ + URL getUrl() const { return url; } + + /** The time when the permissions were persisted, in milliseconds since January 1, 1970 00:00:00.0 UTC. */ + int64 getPersistedTime() const { return time; } + + /** True if the permission allows read access. */ + bool isReadPermission() const { return read; } + + /** True if the permission allows write access. */ + bool isWritePermission() const { return write; } + + /** Gives your app access to a particular document or tree, even after the device is rebooted. + + If you want to persist access to a folder selected through a native file chooser, make sure + to pass the exact URL returned by the file picker. Do NOT call AndroidDocument::fromTree + and then pass the result of getUrl to this function, as the resulting URL may differ from + the result of the file picker. + */ + static void takePersistentReadWriteAccess (const URL&); + + /** Revokes persistent access to a document or tree. */ + static void releasePersistentReadWriteAccess (const URL&); + + /** Returns all of the permissions that have previously been granted to the app, via + takePersistentReadWriteAccess(); + */ + static std::vector getPersistedPermissions(); + +private: + URL url; + int64 time = 0; + bool read = false, write = false; +}; + +//============================================================================== +/** + Provides access to a document on Android devices. + + In this context, a 'document' may be a file or a directory. + + The main purpose of this class is to provide access to files in shared storage on Android. + On newer Android versions, such files cannot be accessed directly by a file path, and must + instead be read and modified using a new URI-based DocumentsContract API. + + Example use-cases: + + - After showing the system open dialog to allow the user to open a file, pass the FileChooser's + URL result to AndroidDocument::fromDocument. Then, you can use getInfo() to retrieve + information about the file, and createInputStream to read from the file. Other functions allow + moving, copying, and deleting the file. + + - Similarly to the 'open' use-case, you may use createOutputStream to write to a file, normally + located using the system save dialog. + + - To allow reading or writing to a tree of files in shared storage, you can show the system + open dialog in 'selects directories' mode, and pass the resulting URL to + AndroidDocument::fromTree. Then, you can iterate the files in the directory, query them, + and create new files. This is a good way to store multiple files that the user can access from + other apps, and that will be persistent after uninstalling and reinstalling your app. + + Note that you probably do *not* need this class if your app only needs to access files in its + own internal sandbox. juce::File instances should work as expected in that case. + + AndroidDocument is a bit like the DocumentFile class from the androidx extension library, + in that it represents a single document, and is implemented using DocumentsContract functions. + + @tags{Core} +*/ +class AndroidDocument +{ +public: + /** Create a null document. */ + AndroidDocument(); + + /** Create an AndroidDocument representing a file or directory at a particular path. + + This is provided for use on older API versions (lower than 19), or on other platforms, so + that the same AndroidDocument API can be used regardless of the runtime platform version. + + If the runtime platform version is 19 or higher, and you wish to work with a URI obtained + from a native file picker, use fromDocument() or fromTree() instead. + + If this function fails, hasValue() will return false on the returned document. + */ + static AndroidDocument fromFile (const File& filePath); + + /** Create an AndroidDocument representing a single document. + + The argument should be a URL representing a document. Such URLs are returned by the system + file-picker when it is not in folder-selection mode. If you pass a tree URL, this function + will fail. + + This function may fail on Android devices with API level 18 or lower, and on non-Android + platforms. If this function fails, hasValue() will return false on the returned document. + If calling this function fails, you may want to retry creating an AndroidDocument + with fromFile(), passing the result of URL::getLocalFile(). + */ + static AndroidDocument fromDocument (const URL& documentUrl); + + /** Create an AndroidDocument representing the root of a tree of files. + + The argument should be a URL representing a tree. Such URLs are returned by the system + file-picker when it is in folder-selection mode. If you pass a URL referring to a document + inside a tree, this will return a document referring to the root of the tree. If you pass + a URL referring to a single file, this will fail. + + When targeting platform version 30 or later, access to the filesystem via file paths is + heavily restricted, and access to shared storage must use a new URI-based system instead. + At time of writing, apps uploaded to the Play Store must target API 30 or higher. + If you want read/write access to a shared folder, you must: + + - Use a native FileChooser in canSelectDirectories mode, to allow the user to select a + folder that your app can access. Your app will only have access to the contents of this + directory; it cannot escape to the filesystem root. The system will not allow the user + to grant access to certain locations, including filesystem roots and the Download folder. + - Pass the URI that the user selected to fromTree(), and use the resulting AndroidDocument + to read/write to the file system. + + This function may fail on Android devices with API level 20 or lower, and on non-Android + platforms. If this function fails, hasValue() will return false on the returned document. + */ + static AndroidDocument fromTree (const URL& treeUrl); + + AndroidDocument (const AndroidDocument&); + AndroidDocument (AndroidDocument&&) noexcept; + + AndroidDocument& operator= (const AndroidDocument&); + AndroidDocument& operator= (AndroidDocument&&) noexcept; + + ~AndroidDocument(); + + /** True if the URLs of the two documents match. */ + bool operator== (const AndroidDocument&) const; + + /** False if the URLs of the two documents match. */ + bool operator!= (const AndroidDocument&) const; + + /** Attempts to delete this document, and returns true on success. */ + bool deleteDocument() const; + + /** Renames the document, and returns true on success. + + This may cause the document's URI and metadata to change, so ensure to invalidate any + cached information about the document (URLs, AndroidDocumentInfo instances) after calling + this function. + */ + bool renameTo (const String& newDisplayName); + + /** Attempts to create a new nested document with a particular type and name. + + The type should be a standard MIME type string, e.g. "image/png", "text/plain". + + The file name doesn't need to contain an extension, as this information is passed via the + type argument. If this document is File-based rather than URL-based, then an appropriate + file extension will be chosen based on the MIME type. + + On failure, the returned AndroidDocument may be invalid, and will return false from hasValue(). + */ + AndroidDocument createChildDocumentWithTypeAndName (const String& type, const String& name) const; + + /** Attempts to create a new nested directory with a particular name. + + On failure, the returned AndroidDocument may be invalid, and will return false from hasValue(). + */ + AndroidDocument createChildDirectory (const String& name) const; + + /** True if this object actually refers to a document. + + If this function returns false, you *must not* call any function on this instance other + than the special member functions to copy, move, and/or destruct the instance. + */ + bool hasValue() const { return pimpl != nullptr; } + + /** Like hasValue(), but allows declaring AndroidDocument instances directly in 'if' statements. */ + explicit operator bool() const { return hasValue(); } + + /** Creates a stream for reading from this document. */ + std::unique_ptr createInputStream() const; + + /** Creates a stream for writing to this document. */ + std::unique_ptr createOutputStream() const; + + /** Returns the content URL describing this document. */ + URL getUrl() const; + + /** Fetches information about this document. */ + AndroidDocumentInfo getInfo() const; + + /** Experimental: Attempts to copy this document to a new parent, and returns an AndroidDocument + representing the copy. + + On failure, the returned AndroidDocument may be invalid, and will return false from hasValue(). + + This function may fail if the document doesn't allow copying, and when using URI-based + documents on devices with API level 23 or lower. On failure, the returned AndroidDocument + will return false from hasValue(). In testing, copying was not supported on the Android + emulator for API 24, 30, or 31, so there's a good chance this function won't work on real + devices. + + @see AndroidDocumentInfo::canCopy + */ + AndroidDocument copyDocumentToParentDocument (const AndroidDocument& target) const; + + /** Experimental: Attempts to move this document from one parent to another, and returns true on + success. + + This may cause the document's URI and metadata to change, so ensure to invalidate any + cached information about the document (URLs, AndroidDocumentInfo instances) after calling + this function. + + This function may fail if the document doesn't allow moving, and when using URI-based + documents on devices with API level 23 or lower. + */ + bool moveDocumentFromParentToParent (const AndroidDocument& currentParent, + const AndroidDocument& newParent); + + /** @internal */ + struct NativeInfo; + + /** @internal */ + NativeInfo getNativeInfo() const; + +private: + struct Utils; + class Pimpl; + + explicit AndroidDocument (std::unique_ptr); + + void swap (AndroidDocument& other) noexcept { std::swap (other.pimpl, pimpl); } + + std::unique_ptr pimpl; +}; + +//============================================================================== +/** + An iterator that visits child documents in a directory. + + Instances of this iterator can be created by calling makeRecursive() or + makeNonRecursive(). The results of these functions can additionally be used + in standard algorithms, and in range-for loops: + + @code + AndroidDocument findFileWithName (const AndroidDocument& parent, const String& name) + { + for (const auto& child : AndroidDocumentIterator::makeNonRecursive (parent)) + if (child.getInfo().getName() == name) + return child; + + return AndroidDocument(); + } + + std::vector findAllChildrenRecursive (const AndroidDocument& parent) + { + std::vector children; + std::copy (AndroidDocumentIterator::makeRecursive (doc), + AndroidDocumentIterator(), + std::back_inserter (children)); + return children; + } + @endcode + + @tags{Core} +*/ +class AndroidDocumentIterator final +{ +public: + using difference_type = std::ptrdiff_t; + using pointer = void; + using iterator_category = std::input_iterator_tag; + + /** Create an iterator that will visit each item in this directory. */ + static AndroidDocumentIterator makeNonRecursive (const AndroidDocument&); + + /** Create an iterator that will visit each item in this directory, and all nested directories. */ + static AndroidDocumentIterator makeRecursive (const AndroidDocument&); + + /** Creates an end/sentinel iterator. */ + AndroidDocumentIterator() = default; + + bool operator== (const AndroidDocumentIterator& other) const noexcept { return pimpl == nullptr && other.pimpl == nullptr; } + bool operator!= (const AndroidDocumentIterator& other) const noexcept { return ! operator== (other); } + + /** Returns the document to which this iterator points. */ + AndroidDocument operator*() const; + + /** Moves this iterator to the next position. */ + AndroidDocumentIterator& operator++(); + + /** Allows this iterator to be used directly in a range-for. */ + AndroidDocumentIterator begin() const { return *this; } + + /** Allows this iterator to be used directly in a range-for. */ + AndroidDocumentIterator end() const { return AndroidDocumentIterator{}; } + +private: + struct Utils; + struct Pimpl; + + explicit AndroidDocumentIterator (std::unique_ptr); + + std::shared_ptr pimpl; +}; + +} // namespace juce diff --git a/source/modules/juce_core/files/juce_File.cpp b/source/modules/juce_core/files/juce_File.cpp index 1ce3c8835..20561f395 100644 --- a/source/modules/juce_core/files/juce_File.cpp +++ b/source/modules/juce_core/files/juce_File.cpp @@ -1146,11 +1146,18 @@ public: expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4")); + expect (! File().hasReadAccess()); + expect (! File().hasWriteAccess()); + + expect (! tempFile.hasReadAccess()); + { FileOutputStream fo (tempFile); fo.write ("0123456789", 10); } + expect (tempFile.hasReadAccess()); + expect (tempFile.exists()); expect (tempFile.getSize() == 10); expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000); diff --git a/source/modules/juce_core/files/juce_File.h b/source/modules/juce_core/files/juce_File.h index baa816c8b..f81ec27f8 100644 --- a/source/modules/juce_core/files/juce_File.h +++ b/source/modules/juce_core/files/juce_File.h @@ -342,6 +342,12 @@ public: */ bool hasWriteAccess() const; + /** Checks whether a file can be read. + + @returns true if it's possible to read this file. + */ + bool hasReadAccess() const; + /** Changes the write-permission of a file or directory. @param shouldBeReadOnly whether to add or remove write-permission diff --git a/source/modules/juce_gui_basics/native/juce_common_MimeTypes.cpp b/source/modules/juce_core/files/juce_common_MimeTypes.cpp similarity index 94% rename from source/modules/juce_gui_basics/native/juce_common_MimeTypes.cpp rename to source/modules/juce_core/files/juce_common_MimeTypes.cpp index 006075af6..656600745 100644 --- a/source/modules/juce_gui_basics/native/juce_common_MimeTypes.cpp +++ b/source/modules/juce_core/files/juce_common_MimeTypes.cpp @@ -33,17 +33,34 @@ struct MimeTypeTableEntry static MimeTypeTableEntry table[641]; }; -static StringArray getMimeTypesForFileExtension (const String& fileExtension) +static StringArray getMatches (const String& toMatch, + const char* MimeTypeTableEntry::* matchField, + const char* MimeTypeTableEntry::* returnField) { StringArray result; for (auto type : MimeTypeTableEntry::table) - if (fileExtension == type.fileExtension) - result.add (type.mimeType); + if (toMatch == type.*matchField) + result.add (type.*returnField); return result; } +namespace MimeTypeTable +{ + +StringArray getMimeTypesForFileExtension (const String& fileExtension) +{ + return getMatches (fileExtension, &MimeTypeTableEntry::fileExtension, &MimeTypeTableEntry::mimeType); +} + +StringArray getFileExtensionsForMimeType (const String& mimeType) +{ + return getMatches (mimeType, &MimeTypeTableEntry::mimeType, &MimeTypeTableEntry::fileExtension); +} + +} // namespace MimeTypeTable + //============================================================================== MimeTypeTableEntry MimeTypeTableEntry::table[641] = { diff --git a/source/modules/juce_graphics/native/juce_android_IconHelpers.cpp b/source/modules/juce_core/files/juce_common_MimeTypes.h similarity index 53% rename from source/modules/juce_graphics/native/juce_android_IconHelpers.cpp rename to source/modules/juce_core/files/juce_common_MimeTypes.h index 019da9d3b..1d79b41bd 100644 --- a/source/modules/juce_graphics/native/juce_android_IconHelpers.cpp +++ b/source/modules/juce_core/files/juce_common_MimeTypes.h @@ -2,15 +2,15 @@ ============================================================================== This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited + Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. + By using JUCE, you agree to the terms of both the JUCE 6 End-User License + Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). - End User License Agreement: www.juce.com/juce-7-licence + End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see @@ -23,8 +23,20 @@ ============================================================================== */ +#pragma once + namespace juce { - Image JUCE_API getIconFromApplication (const String&, int); - Image JUCE_API getIconFromApplication (const String&, int) { return {}; } -} + +namespace MimeTypeTable +{ + +/* @internal */ +StringArray getMimeTypesForFileExtension (const String& fileExtension); + +/* @internal */ +StringArray getFileExtensionsForMimeType (const String& mimeType); + +} // namespace MimeTypeTable + +} // namespace juce diff --git a/source/modules/juce_core/juce_core.cpp b/source/modules/juce_core/juce_core.cpp index 9c4dfd155..f6fbfce2f 100644 --- a/source/modules/juce_core/juce_core.cpp +++ b/source/modules/juce_core/juce_core.cpp @@ -127,13 +127,13 @@ //============================================================================== #include "containers/juce_AbstractFifo.cpp" -// #include "containers/juce_ArrayBase.cpp" -// #include "containers/juce_ListenerList.cpp" +#include "containers/juce_ArrayBase.cpp" +#include "containers/juce_ListenerList.cpp" #include "containers/juce_NamedValueSet.cpp" -// #include "containers/juce_OwnedArray.cpp" +#include "containers/juce_OwnedArray.cpp" #include "containers/juce_PropertySet.cpp" -// #include "containers/juce_ReferenceCountedArray.cpp" -// #include "containers/juce_SparseSet.cpp" +#include "containers/juce_ReferenceCountedArray.cpp" +#include "containers/juce_SparseSet.cpp" #include "files/juce_DirectoryIterator.cpp" #include "files/juce_RangedDirectoryIterator.cpp" #include "files/juce_File.cpp" @@ -144,17 +144,17 @@ #include "logging/juce_FileLogger.cpp" #include "logging/juce_Logger.cpp" #include "maths/juce_BigInteger.cpp" -// #include "maths/juce_Expression.cpp" +#include "maths/juce_Expression.cpp" #include "maths/juce_Random.cpp" #include "memory/juce_MemoryBlock.cpp" -// #include "memory/juce_AllocationHooks.cpp" -// #include "misc/juce_RuntimePermissions.cpp" +#include "memory/juce_AllocationHooks.cpp" +#include "misc/juce_RuntimePermissions.cpp" #include "misc/juce_Result.cpp" -// #include "misc/juce_Uuid.cpp" -// #include "misc/juce_ConsoleApplication.cpp" +#include "misc/juce_Uuid.cpp" +#include "misc/juce_ConsoleApplication.cpp" #include "network/juce_MACAddress.cpp" -// #include "network/juce_NamedPipe.cpp" -// #include "network/juce_Socket.cpp" +#include "network/juce_NamedPipe.cpp" +#include "network/juce_Socket.cpp" #include "network/juce_IPAddress.cpp" #include "streams/juce_BufferedInputStream.cpp" #include "streams/juce_FileInputSource.cpp" @@ -171,27 +171,27 @@ #include "text/juce_StringArray.cpp" #include "text/juce_StringPairArray.cpp" #include "text/juce_StringPool.cpp" -// #include "text/juce_TextDiff.cpp" +#include "text/juce_TextDiff.cpp" #include "text/juce_Base64.cpp" #include "threads/juce_ReadWriteLock.cpp" #include "threads/juce_Thread.cpp" #include "threads/juce_ThreadPool.cpp" -// #include "threads/juce_TimeSliceThread.cpp" -// #include "time/juce_PerformanceCounter.cpp" +#include "threads/juce_TimeSliceThread.cpp" +#include "time/juce_PerformanceCounter.cpp" #include "time/juce_RelativeTime.cpp" #include "time/juce_Time.cpp" -// #include "unit_tests/juce_UnitTest.cpp" +#include "unit_tests/juce_UnitTest.cpp" #include "containers/juce_Variant.cpp" #include "javascript/juce_JSON.cpp" -// #include "javascript/juce_Javascript.cpp" +#include "javascript/juce_Javascript.cpp" #include "containers/juce_DynamicObject.cpp" #include "xml/juce_XmlDocument.cpp" #include "xml/juce_XmlElement.cpp" #include "zip/juce_GZIPDecompressorInputStream.cpp" #include "zip/juce_GZIPCompressorOutputStream.cpp" -// #include "zip/juce_ZipFile.cpp" -// #include "files/juce_FileFilter.cpp" -// #include "files/juce_WildcardFileFilter.cpp" +#include "zip/juce_ZipFile.cpp" +#include "files/juce_FileFilter.cpp" +#include "files/juce_WildcardFileFilter.cpp" //============================================================================== #if ! JUCE_WINDOWS @@ -236,6 +236,7 @@ //============================================================================== #elif JUCE_ANDROID #include "native/juce_linux_CommonFile.cpp" + #include "native/juce_android_AndroidDocument.cpp" #include "native/juce_android_JNIHelpers.cpp" #include "native/juce_android_Files.cpp" #include "native/juce_android_Misc.cpp" @@ -249,6 +250,8 @@ #endif +#include "files/juce_common_MimeTypes.h" +#include "files/juce_common_MimeTypes.cpp" #include "threads/juce_HighResolutionTimer.cpp" #include "threads/juce_WaitableEvent.cpp" #include "network/juce_URL.cpp" @@ -263,7 +266,6 @@ #if JUCE_UNIT_TESTS #include "containers/juce_HashMap_test.cpp" - #include "containers/juce_Optional.h" #include "containers/juce_Optional_test.cpp" #endif diff --git a/source/modules/juce_core/juce_core.h b/source/modules/juce_core/juce_core.h index f461221a4..4f6b2838f 100644 --- a/source/modules/juce_core/juce_core.h +++ b/source/modules/juce_core/juce_core.h @@ -32,7 +32,7 @@ ID: juce_core vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE core classes description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality. website: http://www.juce.com/juce @@ -244,6 +244,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC #include "memory/juce_ReferenceCountedObject.h" #include "memory/juce_ScopedPointer.h" #include "memory/juce_OptionalScopedPointer.h" +#include "containers/juce_Optional.h" #include "containers/juce_ScopedValueSetter.h" #include "memory/juce_Singleton.h" #include "memory/juce_WeakReference.h" @@ -342,6 +343,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC #include "memory/juce_SharedResourcePointer.h" #include "memory/juce_AllocationHooks.h" #include "memory/juce_Reservoir.h" +#include "files/juce_AndroidDocument.h" #if JUCE_CORE_INCLUDE_OBJC_HELPERS && (JUCE_MAC || JUCE_IOS) #include "native/juce_mac_ObjCHelpers.h" diff --git a/source/modules/juce_core/native/juce_BasicNativeHeaders.h b/source/modules/juce_core/native/juce_BasicNativeHeaders.h index cc6eb4737..c45ac6e3b 100644 --- a/source/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/source/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -154,6 +154,8 @@ #include #include #include + #include + #include #if ! JUCE_CXX17_IS_AVAILABLE #pragma push_macro ("WIN_NOEXCEPT") diff --git a/source/modules/juce_core/native/juce_android_Files.cpp b/source/modules/juce_core/native/juce_android_Files.cpp deleted file mode 100644 index 8de4bbeae..000000000 --- a/source/modules/juce_core/native/juce_android_Files.cpp +++ /dev/null @@ -1,691 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -#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") \ - METHOD (scanFile, "scanFile", "(Ljava/lang/String;Ljava/lang/String;)V") \ - -DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection") -#undef JNI_CLASS_MEMBERS - -#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;") - -DECLARE_JNI_CLASS (ContentResolver, "android/content/ContentResolver") -#undef JNI_CLASS_MEMBERS - -#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;") \ - METHOD (close, "close", "()V") \ - -DECLARE_JNI_CLASS (AndroidCursor, "android/database/Cursor") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - METHOD (close, "close", "()V") \ - METHOD (flush, "flush", "()V") \ - METHOD (write, "write", "([BII)V") - -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 == nullptr) - { - // 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 -{ -public: - static LocalRef getStreamForContentUri (const URL& url, bool inputStream) - { - // only use this method for content URIs - jassert (url.getScheme() == "content"); - auto* env = getEnv(); - - LocalRef contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); - - if (contentResolver) - return LocalRef ((env->CallObjectMethod (contentResolver.get(), - inputStream ? ContentResolver.openInputStream - : ContentResolver.openOutputStream, - urlToUri (url).get()))); - - return LocalRef(); - } - - static File getLocalFileFromContentUri (const URL& url) - { - // only use this method for content URIs - jassert (url.getScheme() == "content"); - - auto authority = url.getDomain(); - auto documentId = URL::removeEscapeChars (url.getSubPath().fromFirstOccurrenceOf ("/", false, false)); - auto tokens = StringArray::fromTokens (documentId, ":", ""); - - if (authority == "com.android.externalstorage.documents") - { - auto storageId = tokens[0]; - auto subpath = tokens[1]; - - auto storagePath = getStorageDevicePath (storageId); - - if (storagePath != File()) - return storagePath.getChildFile (subpath); - } - else if (authority == "com.android.providers.downloads.documents") - { - auto type = tokens[0]; - auto downloadId = tokens[1]; - - if (type.equalsIgnoreCase ("raw")) - { - return File (downloadId); - } - else if (type.equalsIgnoreCase ("downloads")) - { - auto subDownloadPath = url.getSubPath().fromFirstOccurrenceOf ("tree/downloads", false, false); - return File (getWellKnownFolder ("DIRECTORY_DOWNLOADS").getFullPathName() + "/" + subDownloadPath); - } - else - { - return getLocalFileFromContentUri (URL ("content://downloads/public_downloads/" + documentId)); - } - } - else if (authority == "com.android.providers.media.documents" && documentId.isNotEmpty()) - { - auto type = tokens[0]; - auto mediaId = tokens[1]; - - if (type == "image") - type = "images"; - - return getCursorDataColumn (URL ("content://media/external/" + type + "/media"), - "_id=?", StringArray {mediaId}); - } - - return getCursorDataColumn (url); - } - - static String getFileNameFromContentUri (const URL& url) - { - auto uri = urlToUri (url); - auto* env = getEnv(); - LocalRef contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); - - if (contentResolver == nullptr) - return {}; - - auto filename = getStringUsingDataColumn ("_display_name", env, uri, contentResolver); - - // Fallback to "_data" column - if (filename.isEmpty()) - { - auto path = getStringUsingDataColumn ("_data", env, uri, contentResolver); - filename = path.fromLastOccurrenceOf ("/", false, true); - } - - return filename; - } - -private: - //============================================================================== - static String getCursorDataColumn (const URL& url, const String& selection = {}, - const StringArray& selectionArgs = {}) - { - auto uri = urlToUri (url); - auto* env = getEnv(); - LocalRef contentResolver (env->CallObjectMethod (getAppContext().get(), AndroidContext.getContentResolver)); - - if (contentResolver) - { - LocalRef columnName (javaString ("_data")); - LocalRef projection (env->NewObjectArray (1, JavaString, columnName.get())); - - LocalRef args; - - if (selection.isNotEmpty()) - { - args = LocalRef (env->NewObjectArray (selectionArgs.size(), JavaString, javaString ("").get())); - - for (int i = 0; i < selectionArgs.size(); ++i) - env->SetObjectArrayElement (args.get(), i, javaString (selectionArgs[i]).get()); - } - - LocalRef jSelection (selection.isNotEmpty() ? javaString (selection) : LocalRef()); - LocalRef cursor (env->CallObjectMethod (contentResolver.get(), ContentResolver.query, - uri.get(), projection.get(), jSelection.get(), - args.get(), nullptr)); - - if (jniCheckHasExceptionOccurredAndClear()) - { - // An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission? - jassertfalse; - return {}; - } - - if (cursor) - { - if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0) - { - auto columnIndex = env->CallIntMethod (cursor.get(), AndroidCursor.getColumnIndex, columnName.get()); - - if (columnIndex >= 0) - { - LocalRef value ((jstring) env->CallObjectMethod (cursor.get(), AndroidCursor.getString, columnIndex)); - - if (value) - return juceString (value.get()); - } - } - - env->CallVoidMethod (cursor.get(), AndroidCursor.close); - } - } - - return {}; - } - - //============================================================================== - static File getStorageDevicePath (const String& storageId) - { - // check for the primary alias - if (storageId == "primary") - return getPrimaryStorageDirectory(); - - auto storageDevices = getSecondaryStorageDirectories(); - - for (auto storageDevice : storageDevices) - if (getStorageIdForMountPoint (storageDevice) == storageId) - return storageDevice; - - return {}; - } - - static File getPrimaryStorageDirectory() - { - auto* env = getEnv(); - return juceFile (LocalRef (env->CallStaticObjectMethod (AndroidEnvironment, AndroidEnvironment.getExternalStorageDirectory))); - } - - static Array getSecondaryStorageDirectories() - { - Array results; - - if (getAndroidSDKVersion() >= 19) - { - auto* env = getEnv(); - static jmethodID m = (env->GetMethodID (AndroidContext, "getExternalFilesDirs", - "(Ljava/lang/String;)[Ljava/io/File;")); - if (m == nullptr) - return {}; - - auto paths = convertFileArray (LocalRef (env->CallObjectMethod (getAppContext().get(), m, nullptr))); - - for (auto path : paths) - results.add (getMountPointForFile (path)); - } - else - { - // on older SDKs other external storages are located "next" to the primary - // storage mount point - auto mountFolder = getMountPointForFile (getPrimaryStorageDirectory()) - .getParentDirectory(); - - // don't include every folder. Only folders which are actually mountpoints - juce_statStruct info; - if (! juce_stat (mountFolder.getFullPathName(), info)) - return {}; - - auto rootFsDevice = info.st_dev; - - for (const auto& iter : RangedDirectoryIterator (mountFolder, false, "*", File::findDirectories)) - { - auto candidate = iter.getFile(); - - if (juce_stat (candidate.getFullPathName(), info) - && info.st_dev != rootFsDevice) - results.add (candidate); - } - - } - - return results; - } - - //============================================================================== - static String getStorageIdForMountPoint (const File& mountpoint) - { - // currently this seems to work fine, but something - // more intelligent may be needed in the future - return mountpoint.getFileName(); - } - - static File getMountPointForFile (const File& file) - { - juce_statStruct info; - - if (juce_stat (file.getFullPathName(), info)) - { - auto dev = info.st_dev; - File mountPoint = file; - - for (;;) - { - auto parent = mountPoint.getParentDirectory(); - - if (parent == mountPoint) - break; - - juce_stat (parent.getFullPathName(), info); - - if (info.st_dev != dev) - break; - - mountPoint = parent; - } - - return mountPoint; - } - - return {}; - } - - //============================================================================== - static Array convertFileArray (LocalRef obj) - { - auto* env = getEnv(); - int n = (int) env->GetArrayLength ((jobjectArray) obj.get()); - Array files; - - for (int i = 0; i < n; ++i) - files.add (juceFile (LocalRef (env->GetObjectArrayElement ((jobjectArray) obj.get(), - (jsize) i)))); - - return files; - } - - //============================================================================== - static String getStringUsingDataColumn (const String& columnNameToUse, JNIEnv* env, - const LocalRef& uri, - const LocalRef& contentResolver) - { - LocalRef columnName (javaString (columnNameToUse)); - LocalRef projection (env->NewObjectArray (1, JavaString, columnName.get())); - - LocalRef cursor (env->CallObjectMethod (contentResolver.get(), ContentResolver.query, - uri.get(), projection.get(), nullptr, - nullptr, nullptr)); - - if (jniCheckHasExceptionOccurredAndClear()) - { - // An exception has occurred, have you acquired RuntimePermission::readExternalStorage permission? - jassertfalse; - return {}; - } - - if (cursor == nullptr) - return {}; - - String fileName; - - if (env->CallBooleanMethod (cursor.get(), AndroidCursor.moveToFirst) != 0) - { - auto columnIndex = env->CallIntMethod (cursor.get(), AndroidCursor.getColumnIndex, columnName.get()); - - if (columnIndex >= 0) - { - LocalRef value ((jstring) env->CallObjectMethod (cursor.get(), AndroidCursor.getString, columnIndex)); - - if (value) - fileName = juceString (value.get()); - - } - } - - env->CallVoidMethod (cursor.get(), AndroidCursor.close); - - return fileName; - } -}; - -//============================================================================== -struct AndroidContentUriOutputStream : public OutputStream -{ - AndroidContentUriOutputStream (LocalRef&& outputStream) - : stream (outputStream) - { - } - - ~AndroidContentUriOutputStream() override - { - stream.callVoidMethod (AndroidOutputStream.close); - } - - void flush() override - { - stream.callVoidMethod (AndroidOutputStream.flush); - } - - bool setPosition (int64 newPos) override - { - return (newPos == pos); - } - - int64 getPosition() override - { - return pos; - } - - bool write (const void* dataToWrite, size_t numberOfBytes) override - { - if (numberOfBytes == 0) - return true; - - JNIEnv* env = getEnv(); - - jbyteArray javaArray = env->NewByteArray ((jsize) numberOfBytes); - env->SetByteArrayRegion (javaArray, 0, (jsize) numberOfBytes, (const jbyte*) dataToWrite); - - stream.callVoidMethod (AndroidOutputStream.write, javaArray, 0, (jint) numberOfBytes); - env->DeleteLocalRef (javaArray); - - pos += static_cast (numberOfBytes); - return true; - } - - GlobalRef stream; - int64 pos = 0; -}; - -static OutputStream* juce_CreateContentURIOutputStream (const URL& url) -{ - auto stream = AndroidContentUriResolver::getStreamForContentUri (url, false); - - return (stream.get() != nullptr ? new AndroidContentUriOutputStream (std::move (stream)) : nullptr); -} - -//============================================================================== -class MediaScannerConnectionClient : public AndroidInterfaceImplementer -{ -public: - virtual void onMediaScannerConnected() = 0; - virtual void onScanCompleted() = 0; - -private: - jobject invoke (jobject proxy, jobject method, jobjectArray args) override - { - auto* env = getEnv(); - - auto methodName = juceString ((jstring) env->CallObjectMethod (method, JavaMethod.getName)); - - if (methodName == "onMediaScannerConnected") - { - onMediaScannerConnected(); - return nullptr; - } - else if (methodName == "onScanCompleted") - { - onScanCompleted(); - return nullptr; - } - - return AndroidInterfaceImplementer::invoke (proxy, method, args); - } -}; - -//============================================================================== -bool File::isOnCDRomDrive() const -{ - return false; -} - -bool File::isOnHardDisk() const -{ - return true; -} - -bool File::isOnRemovableDrive() const -{ - return false; -} - -String File::getVersion() const -{ - return {}; -} - -static File getDocumentsDirectory() -{ - 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) -{ - switch (type) - { - case userHomeDirectory: - case userApplicationDataDirectory: - case userDesktopDirectory: - case commonApplicationDataDirectory: - { - static File appDataDir = getAppDataDir (true); - return appDataDir; - } - - case userDocumentsDirectory: - 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 = getSpecialLocation (commonApplicationDataDirectory).getChildFile (".temp"); - tmp.createDirectory(); - return File (tmp.getFullPathName()); - } - - case invokedExecutableFile: - case currentExecutableFile: - case currentApplicationFile: - case hostApplicationPath: - return getAppDataDir (false); - - default: - jassertfalse; // unknown type? - break; - } - - return {}; -} - -bool File::moveToTrash() const -{ - if (! exists()) - return true; - - // TODO - return false; -} - -JUCE_API bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String&) -{ - URL targetURL (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; -} - -void File::revealToUser() const -{ -} - -//============================================================================== -class SingleMediaScanner : public MediaScannerConnectionClient -{ -public: - SingleMediaScanner (const String& filename) - : msc (LocalRef (getEnv()->NewObject (MediaScannerConnection, - MediaScannerConnection.constructor, - getAppContext().get(), - CreateJavaInterface (this, "android/media/MediaScannerConnection$MediaScannerConnectionClient").get()))), - file (filename) - { - getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.connect); - } - - void onMediaScannerConnected() override - { - auto* env = getEnv(); - - env->CallVoidMethod (msc.get(), MediaScannerConnection.scanFile, javaString (file).get(), 0); - } - - void onScanCompleted() override - { - getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.disconnect); - } - -private: - GlobalRef msc; - String file; -}; - -void FileOutputStream::flushInternal() -{ - if (fileHandle != nullptr) - { - if (fsync (getFD (fileHandle)) == -1) - status = getResultForErrno(); - - // This stuff tells the OS to asynchronously update the metadata - // that the OS has cached about the file - this metadata is used - // when the device is acting as a USB drive, and unless it's explicitly - // refreshed, it'll get out of step with the real file. - new SingleMediaScanner (file.getFullPathName()); - } -} - -} // namespace juce diff --git a/source/modules/juce_core/native/juce_android_JNIHelpers.cpp b/source/modules/juce_core/native/juce_android_JNIHelpers.cpp deleted file mode 100644 index 2741e393e..000000000 --- a/source/modules/juce_core/native/juce_android_JNIHelpers.cpp +++ /dev/null @@ -1,701 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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,215,115,161,94,0,3,105,110,118,111,99,97,116,105,111,110,72,97,110,100,108,101,66,121,116,101,67,111,100,101,46, -100,101,120,0,109,148,65,107,19,65,20,199,223,236,78,146,90,211,116,77,141,214,88,33,151,130,7,117,91,172,80,73,17,161,32,53,93, -17,108,233,65,5,217,38,155,102,219,237,110,220,108,99,172,8,173,40,42,244,36,245,226,65,232,165,138,7,15,226,65,193,147,120,244, -166,130,95,192,155,23,189,21,68,252,207,206,180,137,218,133,223,204,155,247,222,206,123,111,119,230,85,156,86,247,208,201, -83,84,121,127,155,63,122,245,209,24,205,141,63,156,124,186,176,229,110,12,151,158,157,126,219,76,39,136,234,68,212,154,25,201, -146,122,38,56,81,158,164,126,15,248,10,160,162,95,128,129,99,24,82,152,71,152,92,123,24,86,116,162,53,204,203,26,209,29,112, -15,108,128,231,224,37,248,2,126,128,4,252,6,192,56,184,6,102,65,21,220,2,171,224,1,120,2,94,128,215,224,29,248,0,62,129,111,224, -59,248,169,203,184,72,157,146,36,115,233,82,185,118,131,189,160,7,232,138,171,154,204,95,200,53,77,218,83,170,214,180,146, -35,77,238,153,139,107,212,99,27,35,141,122,213,218,80,181,239,83,250,108,60,51,234,139,247,213,148,191,68,188,61,13,149,208,142, -97,24,228,180,99,63,194,69,206,122,44,111,233,50,223,186,33,52,7,32,93,30,2,35,68,25,229,1,95,46,235,140,173,5,97,17,107,153, -97,154,203,245,212,89,216,17,103,24,33,71,81,141,88,215,135,52,226,44,131,90,121,252,141,250,184,172,109,170,222,233,219,67,83, -33,234,191,158,186,155,122,156,218,108,38,69,212,52,106,204,210,209,223,180,243,48,53,139,60,214,88,251,219,203,178,116,236, -223,165,190,117,50,254,15,42,243,49,215,119,163,51,196,74,148,47,45,149,157,243,126,51,40,219,145,27,248,19,182,95,241,156,240, -196,188,221,180,41,97,149,44,203,34,110,137,113,208,42,7,139,102,184,216,240,204,121,188,98,238,250,94,145,242,86,197,246,154, -238,130,105,251,126,16,197,54,115,186,22,6,55,26,69,202,90,98,91,211,179,253,57,243,226,236,188,83,142,138,148,235,208,197,126, -246,172,231,20,17,173,173,14,157,170,7,95,115,215,104,255,187,93,112,162,90,80,41,18,155,33,109,166,68,125,87,118,137,202,237, -112,174,65,137,178,231,216,33,25,21,183,81,183,163,114,237,156,235,219,158,187,236,80,102,91,35,66,46,56,212,85,221,182,36,93, -169,73,46,198,81,168,199,71,66,77,103,60,240,35,167,21,145,241,215,242,146,83,165,68,61,12,90,55,137,71,53,23,1,155,182,183, -132,237,216,193,84,70,203,23,181,185,210,113,156,146,84,102,146,246,99,188,127,153,14,235,253,185,94,72,155,164,105,236,208,0, -235,231,196,116,113,134,87,87,248,186,174,225,246,50,1,123,163,235,236,179,206,216,138,248,207,198,63,103,65,204,219,61,66,235, -232,19,122,71,175,224,29,253,34,65,237,158,145,164,118,223,208,13,41,199,231,170,32,223,89,23,62,5,169,23,247,135,25,82,31,223, -169,130,140,43,250,140,174,252,197,61,226,133,246,253,34,37,15,170,196,133,44,122,218,31,165,24,139,249,12,5,0,0,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/rmsl/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 (nullptr) -{ - 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 == nullptr) - { - for (auto& byteCodeLoader : byteCodeLoaders) - { - tryLoadingClassWithClassLoader (env, byteCodeLoader.get()); - - if (classRef != nullptr) - 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 == nullptr) - classRef = (jclass) env->NewGlobalRef (LocalRef (env->FindClass (classPath))); - - jassert (classRef != nullptr); - 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 = nullptr; - } - - // 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 != nullptr) - 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 != nullptr); - return m; -} - -jmethodID JNIClassBase::resolveStaticMethod (JNIEnv* env, const char* methodName, const char* params) -{ - jmethodID m = env->GetStaticMethodID (classRef, methodName, params); - jassert (m != nullptr); - return m; -} - -jfieldID JNIClassBase::resolveField (JNIEnv* env, const char* fieldName, const char* signature) -{ - jfieldID f = env->GetFieldID (classRef, fieldName, signature); - jassert (f != nullptr); - return f; -} - -jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, const char* signature) -{ - jfieldID f = env->GetStaticFieldID (classRef, fieldName, signature); - jassert (f != nullptr); - 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 == "onActivityPreCreated") { onActivityPreCreated (activity, bundle); return nullptr; } - else if (methodName == "onActivityPreDestroyed") { onActivityPreDestroyed (activity); return nullptr; } - else if (methodName == "onActivityPrePaused") { onActivityPrePaused (activity); return nullptr; } - else if (methodName == "onActivityPreResumed") { onActivityPreResumed (activity); return nullptr; } - else if (methodName == "onActivityPreSaveInstanceState") { onActivityPreSaveInstanceState (activity, bundle); return nullptr; } - else if (methodName == "onActivityPreStarted") { onActivityPreStarted (activity); return nullptr; } - else if (methodName == "onActivityPreStopped") { onActivityPreStopped (activity); return nullptr; } - else 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; } - else if (methodName == "onActivityPostCreated") { onActivityPostCreated (activity, bundle); return nullptr; } - else if (methodName == "onActivityPostDestroyed") { onActivityPostDestroyed (activity); return nullptr; } - else if (methodName == "onActivityPostPaused") { onActivityPostPaused (activity); return nullptr; } - else if (methodName == "onActivityPostResumed") { onActivityPostResumed (activity); return nullptr; } - else if (methodName == "onActivityPostSaveInstanceState") { onActivityPostSaveInstanceState (activity, bundle); return nullptr; } - else if (methodName == "onActivityPostStarted") { onActivityPostStarted (activity); return nullptr; } - else if (methodName == "onActivityPostStopped") { onActivityPostStopped (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 != nullptr); - - auto sdkVersionField = env->GetStaticFieldID (buildVersion, "SDK_INT", "I"); - jassert (sdkVersionField != nullptr); - - 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/rmsl/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,26,116,161,94,0,3,106,97,118,97,70,114,97,103,109,101,110,116,79,118,101,114,108,97,121,46,100,101,120,0,133,149, -77,136,28,69,20,199,255,53,253,181,159,179,147,221,184,140,235,198,140,43,70,197,224,172,104,36,56,99,216,152,32,204,100,226,71, -54,204,97,227,165,153,105,39,189,206,118,79,186,123,150,4,20,53,4,146,131,8,6,252,130,28,114,80,65,48,8,226,65,196,83,8,66,64, -65,146,75,252,184,152,179,160,160,4,17,5,255,175,187,58,27,150,136,195,252,250,189,122,245,234,189,170,215,213,85,93,239,248, -216,226,163,187,96,79,85,156,198,103,91,86,175,30,189,252,253,193,79,203,15,189,242,199,245,246,129,179,245,238,53,27,24,0,56, -222,126,108,26,250,183,155,182,7,145,217,199,200,86,149,201,58,37,255,248,156,143,18,229,87,186,93,47,0,47,155,192,11,148,87, -12,224,7,242,27,249,157,220,32,127,145,127,200,93,244,217,69,154,228,37,242,42,57,73,206,144,55,201,89,242,62,57,79,62,36,31, -147,11,228,34,185,76,174,144,107,228,103,242,43,249,147,216,22,80,38,139,228,9,210,36,47,146,51,228,45,114,158,92,32,95,146,175, -201,183,132,211,4,167,3,46,19,14,25,33,163,122,173,227,100,70,214,76,24,62,93,223,41,58,91,186,13,237,227,104,125,66,235,111, -208,103,82,235,239,81,47,106,253,3,234,83,90,255,196,200,234,38,250,23,212,183,104,253,18,245,105,173,127,147,230,82,152,133, -204,179,144,230,40,112,118,119,235,246,130,158,199,28,196,47,235,23,121,135,150,101,100,227,239,76,165,129,249,84,218,216,150, -202,44,142,197,21,111,79,165,137,74,42,29,220,163,199,47,164,210,194,189,200,214,172,0,157,37,211,229,55,98,103,210,160,69,108, -87,173,172,134,131,146,248,202,204,87,42,82,129,188,255,71,221,159,247,4,37,155,126,69,214,209,76,223,193,117,43,91,255,50,55, -220,44,147,61,194,48,187,217,187,28,177,38,199,212,41,245,182,243,209,186,61,202,88,69,200,72,89,255,47,28,35,107,10,43,10,135, -25,209,161,117,2,115,106,22,65,197,96,149,199,177,178,196,136,75,183,70,116,210,246,96,137,121,159,47,166,239,49,203,127,227, -127,242,59,105,254,201,52,191,212,86,246,142,12,148,247,23,150,100,62,183,205,179,56,5,83,21,117,221,108,189,231,160,101,166, -143,166,117,81,154,124,191,73,111,174,139,71,33,213,77,237,99,215,253,192,79,246,96,235,211,145,219,91,243,130,228,217,117,47, -234,187,39,30,94,117,215,93,168,6,84,19,133,102,11,170,133,249,150,27,116,163,208,239,86,221,193,160,186,223,119,251,97,47,31, -85,67,249,102,111,39,12,18,154,170,141,84,212,48,115,179,39,140,171,79,13,131,110,223,171,97,123,171,19,174,85,163,181,184,95, -93,29,118,188,234,166,244,53,76,183,100,6,213,190,27,244,170,203,73,228,7,189,26,84,27,102,187,209,104,201,179,213,66,161,221, -132,213,110,138,65,4,45,70,187,41,102,114,164,129,153,35,183,9,97,117,250,97,236,193,233,12,6,135,143,250,49,204,174,155,184, -112,186,126,188,230,199,49,38,122,94,178,55,234,13,101,42,49,28,182,90,97,208,163,57,114,131,228,144,23,15,251,52,151,194,96, -111,39,241,215,253,228,68,102,194,236,102,203,51,46,91,30,70,194,96,95,228,185,137,135,98,174,233,158,185,48,56,228,29,27,122, -113,242,156,23,73,106,63,12,98,29,173,242,223,125,122,180,19,6,203,137,27,37,152,212,138,182,143,15,54,6,96,60,202,130,236,11, -187,30,198,162,116,124,170,91,113,34,83,50,19,41,192,54,56,197,194,206,26,246,83,30,168,99,143,177,227,254,178,83,60,253,14,22, -212,3,78,177,126,233,244,10,30,55,118,220,55,79,219,187,216,73,167,39,105,129,178,248,121,155,175,191,102,254,100,90,39,121, -146,220,130,165,254,54,13,117,206,42,168,239,200,57,155,210,158,220,244,205,139,204,239,4,217,143,249,189,96,96,227,110,200,247, -172,220,15,114,118,228,119,132,141,141,123,66,85,178,182,220,21,170,148,157,11,114,190,22,42,89,124,185,63,12,237,35,231,138, -28,80,42,63,115,74,153,46,247,211,191,81,33,150,205,216,6,0,0,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/rmsl/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, nullptr); - - 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/source/modules/juce_core/native/juce_android_JNIHelpers.h b/source/modules/juce_core/native/juce_android_JNIHelpers.h deleted file mode 100644 index 341adef22..000000000 --- a/source/modules/juce_core/native/juce_android_JNIHelpers.h +++ /dev/null @@ -1,1010 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -//============================================================================== -extern JNIEnv* getEnv() noexcept; - -//============================================================================== -template -class LocalRef -{ -public: - explicit inline LocalRef() noexcept : obj (nullptr) {} - explicit inline LocalRef (JavaType o) noexcept : obj (o) {} - inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} - inline LocalRef (LocalRef&& other) noexcept : obj (nullptr) { std::swap (obj, other.obj); } - ~LocalRef() { clear(); } - - void clear() - { - if (obj != nullptr) - { - getEnv()->DeleteLocalRef (obj); - obj = nullptr; - } - } - - 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 == nullptr ? nullptr : (JavaType) getEnv()->NewLocalRef (obj); - } -}; - -//============================================================================== -class GlobalRef -{ -public: - inline GlobalRef() noexcept : obj (nullptr) {} - 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 (nullptr) { std::swap (other.obj, obj); } - ~GlobalRef() { clear(); } - - - inline void clear() { if (obj != nullptr) clear (getEnv()); } - inline void clear (JNIEnv* env) - { - if (obj != nullptr) - { - env->DeleteGlobalRef (obj); - obj = nullptr; - } - } - - inline GlobalRef& operator= (const GlobalRef& other) - { - jobject newObj = retain (other.obj, getEnv()); - clear(); - obj = newObj; - return *this; - } - - inline GlobalRef& operator= (GlobalRef&& other) - { - clear(); - std::swap (obj, other.obj); - - return *this; - } - - //============================================================================== - inline operator jobject() const noexcept { return obj; } - inline jobject get() const noexcept { return obj; } - - //============================================================================== - #define DECLARE_CALL_TYPE_METHOD(returnType, typeName) \ - returnType call##typeName##Method (jmethodID methodID, ... ) const \ - { \ - va_list args; \ - va_start (args, methodID); \ - returnType result = getEnv()->Call##typeName##MethodV (obj, methodID, args); \ - va_end (args); \ - return result; \ - } - - DECLARE_CALL_TYPE_METHOD (jobject, Object) - DECLARE_CALL_TYPE_METHOD (jboolean, Boolean) - DECLARE_CALL_TYPE_METHOD (jbyte, Byte) - DECLARE_CALL_TYPE_METHOD (jchar, Char) - DECLARE_CALL_TYPE_METHOD (jshort, Short) - DECLARE_CALL_TYPE_METHOD (jint, Int) - DECLARE_CALL_TYPE_METHOD (jlong, Long) - DECLARE_CALL_TYPE_METHOD (jfloat, Float) - DECLARE_CALL_TYPE_METHOD (jdouble, Double) - #undef DECLARE_CALL_TYPE_METHOD - - void callVoidMethod (jmethodID methodID, ... ) const - { - va_list args; - va_start (args, methodID); - getEnv()->CallVoidMethodV (obj, methodID, args); - va_end (args); - } - -private: - //============================================================================== - jobject obj = nullptr; - - static jobject retain (jobject obj, JNIEnv* env) - { - return obj == nullptr ? nullptr : env->NewGlobalRef (obj); - } -}; - - -//============================================================================== -extern LocalRef getAppContext() noexcept; -extern LocalRef getCurrentActivity() noexcept; -extern LocalRef getMainActivity() noexcept; - -//============================================================================== -struct SystemJavaClassComparator; -class JNIClassBase -{ -public: - explicit JNIClassBase (const char* classPath, int minSDK, const void* byteCode, size_t byteCodeSize); - virtual ~JNIClassBase(); - - 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; - - jmethodID resolveMethod (JNIEnv*, const char* methodName, const char* params); - 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; - const void* byteCode; - size_t byteCodeSize; - - int minSDK; - jclass classRef = nullptr; - - 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 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, minSDK, byteCodeData, byteCodeSize) {} \ - \ - void initialiseFields (JNIEnv* env) \ - { \ - 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, DECLARE_JNI_CALLBACK) \ - }; \ - static CppClassName ## _Class CppClassName; - -//============================================================================== -#define DECLARE_JNI_CLASS_WITH_MIN_SDK(CppClassName, javaPath, minSDK) \ - DECLARE_JNI_CLASS_WITH_BYTECODE (CppClassName, javaPath, minSDK, nullptr, 0) - -//============================================================================== -#define DECLARE_JNI_CLASS(CppClassName, javaPath) \ - DECLARE_JNI_CLASS_WITH_MIN_SDK (CppClassName, javaPath, 16) - -//============================================================================== -#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 (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 (startActivityForResult, "startActivityForResult", "(Landroid/content/Intent;I)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_WITH_MIN_SDK (AndroidFragment, "android/app/Fragment", 11) -#undef JNI_CLASS_MEMBERS - -//============================================================================== -#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_WITH_MIN_SDK (AndroidAudioAttributesBuilder, "android/media/AudioAttributes$Builder", 21) -#undef JNI_CLASS_MEMBERS - -#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, 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 (getPixels, "getPixels", "([IIIIIII)V") - -DECLARE_JNI_CLASS (AndroidBitmap, "android/graphics/Bitmap") -#undef JNI_CLASS_MEMBERS - -#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, 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, CALLBACK) \ - METHOD (constructor, "", "()V") \ - METHOD (containsKey, "containsKey", "(Ljava/lang/String;)Z") \ - METHOD (get, "get", "(Ljava/lang/String;)Ljava/lang/Object;") \ - METHOD (getBoolean, "getBoolean", "(Ljava/lang/String;)Z") \ - METHOD (getBundle, "getBundle", "(Ljava/lang/String;)Landroid/os/Bundle;") \ - METHOD (getCharSequence, "getCharSequence", "(Ljava/lang/String;)Ljava/lang/CharSequence;") \ - METHOD (getInt, "getInt", "(Ljava/lang/String;)I") \ - METHOD (getLong, "getLong", "(Ljava/lang/String;)J") \ - METHOD (getLongArray, "getLongArray", "(Ljava/lang/String;)[J") \ - METHOD (getParcelable, "getParcelable", "(Ljava/lang/String;)Landroid/os/Parcelable;") \ - METHOD (getString, "getString", "(Ljava/lang/String;)Ljava/lang/String;") \ - METHOD (getStringArrayList, "getStringArrayList", "(Ljava/lang/String;)Ljava/util/ArrayList;") \ - METHOD (keySet, "keySet", "()Ljava/util/Set;") \ - METHOD (putBoolean, "putBoolean", "(Ljava/lang/String;Z)V") \ - METHOD (putBundle, "putBundle", "(Ljava/lang/String;Landroid/os/Bundle;)V") \ - METHOD (putFloat, "putFloat", "(Ljava/lang/String;F)V") \ - METHOD (putInt, "putInt", "(Ljava/lang/String;I)V") \ - METHOD (putLong, "putLong", "(Ljava/lang/String;J)V") \ - METHOD (putLongArray, "putLongArray", "(Ljava/lang/String;[J)V") \ - METHOD (putString, "putString", "(Ljava/lang/String;Ljava/lang/String;)V") \ - METHOD (putStringArrayList, "putStringArrayList", "(Ljava/lang/String;Ljava/util/ArrayList;)V") - -DECLARE_JNI_CLASS (AndroidBundle, "android/os/Bundle") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (dumpReferenceTables, "dumpReferenceTables", "()V") - - DECLARE_JNI_CLASS (AndroidDebug, "android/os/Debug") -#undef JNI_CLASS_MEMBERS - -#define JUCE_LOG_JNI_REFERENCES_TABLE getEnv()->CallStaticVoidMethod (AndroidDebug, AndroidDebug.dumpReferenceTables); - -#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, CALLBACK) \ - METHOD (constructor, "", "()V") \ - METHOD (constructorWithLooper, "", "(Landroid/os/Looper;)V") \ - METHOD (post, "post", "(Ljava/lang/Runnable;)Z") \ - METHOD (postDelayed, "postDelayed", "(Ljava/lang/Runnable;J)Z") \ - -DECLARE_JNI_CLASS (AndroidHandler, "android/os/Handler") -#undef JNI_CLASS_MEMBERS - -#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 (start, "start", "()V") - -DECLARE_JNI_CLASS (AndroidHandlerThread, "android/os/HandlerThread") -#undef JNI_CLASS_MEMBERS - -#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;") \ - METHOD (getExtras, "getExtras", "()Landroid/os/Bundle;") \ - METHOD (getIntExtra, "getIntExtra", "(Ljava/lang/String;I)I") \ - METHOD (getStringExtra, "getStringExtra", "(Ljava/lang/String;)Ljava/lang/String;") \ - METHOD (putExtra, "putExtra", "(Ljava/lang/String;Ljava/lang/CharSequence;)Landroid/content/Intent;") \ - METHOD (putExtras, "putExtras", "(Landroid/os/Bundle;)Landroid/content/Intent;") \ - METHOD (putExtraString, "putExtra", "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;") \ - METHOD (putExtraStrings, "putExtra", "(Ljava/lang/String;[Ljava/lang/String;)Landroid/content/Intent;") \ - METHOD (putExtraParcelable, "putExtra", "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;") \ - METHOD (putParcelableArrayListExtra, "putParcelableArrayListExtra", "(Ljava/lang/String;Ljava/util/ArrayList;)Landroid/content/Intent;") \ - METHOD (setAction, "setAction", "(Ljava/lang/String;)Landroid/content/Intent;") \ - METHOD (setFlags, "setFlags", "(I)Landroid/content/Intent;") \ - METHOD (setPackage, "setPackage", "(Ljava/lang/String;)Landroid/content/Intent;") \ - METHOD (setType, "setType", "(Ljava/lang/String;)Landroid/content/Intent;") \ - -DECLARE_JNI_CLASS (AndroidIntent, "android/content/Intent") -#undef JNI_CLASS_MEMBERS - -#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 (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, 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, 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") \ - METHOD (setTypeface, "setTypeface", "(Landroid/graphics/Typeface;)Landroid/graphics/Typeface;") \ - METHOD (ascent, "ascent", "()F") \ - METHOD (descent, "descent", "()F") \ - METHOD (setTextSize, "setTextSize", "(F)V") \ - 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, 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, CALLBACK) \ - METHOD (toString, "toString", "()Ljava/lang/String;") - -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, CALLBACK) \ - METHOD (constructor, "", "(IIII)V") \ - FIELD (left, "left", "I") \ - FIELD (right, "right", "I") \ - FIELD (top, "top", "I") \ - FIELD (bottom, "bottom", "I") - -DECLARE_JNI_CLASS (AndroidRect, "android/graphics/Rect") -#undef JNI_CLASS_MEMBERS - -#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;") \ - METHOD (getConfiguration, "getConfiguration", "()Landroid/content/res/Configuration;") - -DECLARE_JNI_CLASS (AndroidResources, "android/content/res/Resources") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - FIELD (uiMode, "uiMode", "I") \ - -DECLARE_JNI_CLASS (AndroidConfiguration, "android/content/res/Configuration") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getHeight, "getHeight", "()I") \ - METHOD (getWidth, "getWidth", "()I") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidSize, "android/util/Size", 21) -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - METHOD (construct, "", "(Landroid/content/Context;)V") \ - METHOD (layout, "layout", "(IIII)V") \ - METHOD (getLeft, "getLeft", "()I") \ - METHOD (getTop, "getTop", "()I") \ - METHOD (getWidth, "getWidth", "()I") \ - METHOD (getHeight, "getHeight", "()I") \ - METHOD (getLocationOnScreen, "getLocationOnScreen", "([I)V") \ - METHOD (getParent, "getParent", "()Landroid/view/ViewParent;") \ - METHOD (bringToFront, "bringToFront", "()V") \ - METHOD (requestFocus, "requestFocus", "()Z") \ - METHOD (hasFocus, "hasFocus", "()Z") \ - METHOD (invalidate, "invalidate", "(IIII)V") \ - METHOD (setVisibility, "setVisibility", "(I)V") \ - METHOD (setLayoutParams, "setLayoutParams", "(Landroid/view/ViewGroup$LayoutParams;)V") \ - METHOD (setSystemUiVisibility, "setSystemUiVisibility", "(I)V") \ - METHOD (findViewById, "findViewById", "(I)Landroid/view/View;") \ - METHOD (getRootView, "getRootView", "()Landroid/view/View;") \ - METHOD (addOnLayoutChangeListener, "addOnLayoutChangeListener", "(Landroid/view/View$OnLayoutChangeListener;)V") \ - METHOD (announceForAccessibility, "announceForAccessibility", "(Ljava/lang/CharSequence;)V") \ - -DECLARE_JNI_CLASS (AndroidView, "android/view/View") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (addView, "addView", "(Landroid/view/View;)V") \ - METHOD (removeView, "removeView", "(Landroid/view/View;)V") \ - METHOD (requestSendAccessibilityEvent, "requestSendAccessibilityEvent", "(Landroid/view/View;Landroid/view/accessibility/AccessibilityEvent;)Z") \ - -DECLARE_JNI_CLASS (AndroidViewGroup, "android/view/ViewGroup") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getDecorView, "getDecorView", "()Landroid/view/View;") \ - METHOD (setFlags, "setFlags", "(II)V") \ - METHOD (clearFlags, "clearFlags", "(I)V") - -DECLARE_JNI_CLASS (AndroidWindow, "android/view/Window") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - METHOD (constructor, "", "(I)V") \ - METHOD (add, "add", "(Ljava/lang/Object;)Z") \ - METHOD (iterator, "iterator", "()Ljava/util/Iterator;") \ - METHOD (get, "get", "(I)Ljava/lang/Object;") \ - METHOD (size, "size", "()I") - -DECLARE_JNI_CLASS (JavaArrayList, "java/util/ArrayList") -#undef JNI_CLASS_MEMBERS - -#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, 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, 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, CALLBACK) \ - STATICMETHOD (forName, "forName", "(Ljava/lang/String;)Ljava/lang/Class;") \ - METHOD (getName, "getName", "()Ljava/lang/String;") \ - METHOD (getModifiers, "getModifiers", "()I") \ - METHOD (isAnnotation, "isAnnotation", "()Z") \ - METHOD (isAnonymousClass, "isAnonymousClass", "()Z") \ - METHOD (isArray, "isArray", "()Z") \ - METHOD (isEnum, "isEnum", "()Z") \ - METHOD (isInterface, "isInterface", "()Z") \ - METHOD (isLocalClass, "isLocalClass", "()Z") \ - METHOD (isMemberClass, "isMemberClass", "()Z") \ - METHOD (isPrimitive, "isPrimitive", "()Z") \ - METHOD (isSynthetic, "isSynthetic", "()Z") \ - METHOD (getComponentType, "getComponentType", "()Ljava/lang/Class;") \ - METHOD (getSuperclass, "getSuperclass", "()Ljava/lang/Class;") \ - METHOD (getClassLoader, "getClassLoader", "()Ljava/lang/ClassLoader;") \ - -DECLARE_JNI_CLASS (JavaClass, "java/lang/Class") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - METHOD (constructor, "", "(Ljava/lang/String;)V") \ - METHOD (getAbsolutePath, "getAbsolutePath", "()Ljava/lang/String;") \ - METHOD (length, "length", "()J") - -DECLARE_JNI_CLASS (JavaFile, "java/io/File") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(Ljava/lang/String;)V") \ - METHOD (close, "close", "()V") \ - METHOD (read, "read", "([B)I") - -DECLARE_JNI_CLASS (JavaFileInputStream, "java/io/FileInputStream") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(Ljava/lang/String;)V") \ - METHOD (close, "close", "()V") \ - METHOD (write, "write", "([BII)V") - -DECLARE_JNI_CLASS (JavaFileOutputStream, "java/io/FileOutputStream") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - STATICMETHOD (parseInt, "parseInt", "(Ljava/lang/String;I)I") \ - STATICMETHOD (valueOf, "valueOf", "(I)Ljava/lang/Integer;") \ - METHOD (constructor, "", "(I)V") \ - METHOD (intValue, "intValue", "()I") - -DECLARE_JNI_CLASS (JavaInteger, "java/lang/Integer") -#undef JNI_CLASS_MEMBERS - -#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, 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, CALLBACK) \ - METHOD (constructor, "", "(J)V") - -DECLARE_JNI_CLASS (JavaLong, "java/lang/Long") -#undef JNI_CLASS_MEMBERS - -#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;") - -DECLARE_JNI_CLASS (JavaMap, "java/util/Map") -#undef JNI_CLASS_MEMBERS - -#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;") \ - METHOD (getReturnType, "getReturnType", "()Ljava/lang/Class;") \ - METHOD (invoke, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;") \ - METHOD (hashCode, "hashCode", "()I") \ - METHOD (equals, "equals", "(Ljava/lang/Object;)Z") \ - -DECLARE_JNI_CLASS (JavaMethod, "java/lang/reflect/Method") -#undef JNI_CLASS_MEMBERS - - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "()V") \ - METHOD (getClass, "getClass", "()Ljava/lang/Class;") \ - METHOD (toString, "toString", "()Ljava/lang/String;") - -DECLARE_JNI_CLASS (JavaObject, "java/lang/Object") -#undef JNI_CLASS_MEMBERS - -#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") - -DECLARE_JNI_CLASS (JavaSet, "java/util/Set") -#undef JNI_CLASS_MEMBERS - -#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 -{ - inline String juceString (JNIEnv* env, jstring s) - { - if (s == nullptr) - return {}; - - const char* const utf8 = env->GetStringUTFChars (s, nullptr); - CharPointer_UTF8 utf8CP (utf8); - const String result (utf8CP); - env->ReleaseStringUTFChars (s, utf8); - return result; - } - - inline String juceString (jstring s) - { - return juceString (getEnv(), s); - } - - inline LocalRef javaString (const String& s) - { - return LocalRef (getEnv()->NewStringUTF (s.toUTF8())); - } - - inline LocalRef javaStringFromChar (const juce_wchar c) - { - char utf8[8] = { 0 }; - CharPointer_UTF8 (utf8).write (c); - return LocalRef (getEnv()->NewStringUTF (utf8)); - } - - inline LocalRef juceStringArrayToJava (const StringArray& juceArray) - { - auto* env = getEnv(); - - LocalRef result (env->NewObjectArray ((jsize) juceArray.size(), - JavaString, - javaString ("").get())); - - for (int i = 0; i < juceArray.size(); ++i) - env->SetObjectArrayElement (result, i, javaString (juceArray [i]).get()); - - return result; - } - - inline StringArray javaStringArrayToJuce (const LocalRef& javaArray) - { - if (javaArray.get() == nullptr) - return {}; - - auto* env = getEnv(); - - StringArray result; - - for (int i = 0; i < env->GetArrayLength (javaArray.get()); ++i) - { - LocalRef javaString ((jstring) env->GetObjectArrayElement (javaArray.get(), i)); - result.add (juceString (javaString.get())); - } - - return result; - } - - inline bool jniCheckHasExceptionOccurredAndClear() - { - auto* env = getEnv(); - - LocalRef exception (env->ExceptionOccurred()); - - if (exception != nullptr) - { - env->ExceptionClear(); - return true; - } - - return false; - } -} - -//============================================================================== -int getAndroidSDKVersion(); -bool isPermissionDeclaredInManifest (const String& requestedPermission); - -//============================================================================== -class AndroidInterfaceImplementer; - -// This function takes ownership of the implementer. When the returned GlobalRef -// goes out of scope (and no other Java routine has a reference on the return-value) -// then the implementer will be deleted as well. -LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, - const StringArray& interfaceNames, - LocalRef subclass); - -//============================================================================== -jobject juce_invokeImplementer (JNIEnv*, jobject, jlong, jobject, jobject, jobjectArray); -void juce_dispatchDelete (JNIEnv*, jobject, jlong); - -//============================================================================== -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*, jobject, jlong, jobject, jobject, jobjectArray); - friend void juce_dispatchDelete (JNIEnv*, jobject, jlong); -private: - GlobalRef javaSubClass; - GlobalRef invocationHandler; -}; - -LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, - const StringArray& interfaceNames); -LocalRef CreateJavaInterface (AndroidInterfaceImplementer* implementer, - const String& interfaceName); - -//============================================================================== -class ActivityLifecycleCallbacks : public AndroidInterfaceImplementer -{ -public: - virtual void onActivityPreCreated (jobject /*activity*/, jobject /*bundle*/) {} - virtual void onActivityPreDestroyed (jobject /*activity*/) {} - virtual void onActivityPrePaused (jobject /*activity*/) {} - virtual void onActivityPreResumed (jobject /*activity*/) {} - virtual void onActivityPreSaveInstanceState (jobject /*activity*/, jobject /*bundle*/) {} - virtual void onActivityPreStarted (jobject /*activity*/) {} - virtual void onActivityPreStopped (jobject /*activity*/) {} - - 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*/) {} - - virtual void onActivityPostCreated (jobject /*activity*/, jobject /*bundle*/) {} - virtual void onActivityPostDestroyed (jobject /*activity*/) {} - virtual void onActivityPostPaused (jobject /*activity*/) {} - virtual void onActivityPostResumed (jobject /*activity*/) {} - virtual void onActivityPostSaveInstanceState (jobject /*activity*/, jobject /*bundle*/) {} - virtual void onActivityPostStarted (jobject /*activity*/) {} - virtual void onActivityPostStopped (jobject /*activity*/) {} - -private: - jobject invoke (jobject, jobject, jobjectArray) override; -}; - -//============================================================================== -struct SurfaceHolderCallback : AndroidInterfaceImplementer -{ - virtual ~SurfaceHolderCallback() override = default; - - 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 - { - 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]; - - for (int i = 0; i < 3; ++i) - { - LocalRef boxedType (env->GetObjectArrayElement (args, 1 + i)); - intArgs[i] = env->CallIntMethod (boxedType.get(), JavaInteger.intValue); - } - - 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(); - - 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: - - 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/source/modules/juce_core/native/juce_android_Misc.cpp b/source/modules/juce_core/native/juce_android_Misc.cpp deleted file mode 100644 index 7afd5c678..000000000 --- a/source/modules/juce_core/native/juce_android_Misc.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -void Logger::outputDebugString (const String& text) -{ - char* data = text.toUTF8().getAddress(); - const size_t length = CharPointer_UTF8::getBytesRequiredFor (text.getCharPointer()); - const size_t chunkSize = 1023; - - size_t position = 0; - size_t numToRead = jmin (chunkSize, length); - - while (numToRead > 0) - { - __android_log_print (ANDROID_LOG_INFO, "JUCE", "%s", data + position); - - position += numToRead; - numToRead = jmin (chunkSize, length - position); - } -} - -} // namespace juce diff --git a/source/modules/juce_core/native/juce_android_Network.cpp b/source/modules/juce_core/native/juce_android_Network.cpp deleted file mode 100644 index 35eae530e..000000000 --- a/source/modules/juce_core/native/juce_android_Network.cpp +++ /dev/null @@ -1,656 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -//============================================================================== -// This byte-code is generated from native/java/com/rmsl/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,71,116,161,94,0,3,106,97,118,97,74,117,99,101,72,116,116,112,83,116,114,101,97,109,46,100,101,120,0,125,154,11,124, -84,87,157,199,255,231,222,185,119,30,153,153,220,76,30,147,132,60,38,33,132,4,18,38,80,104,41,9,148,242,42,144,80,40,12,84,8, -91,29,50,23,50,48,185,19,102,238,64,168,180,133,62,233,67,75,223,184,86,173,72,45,186,85,171,86,173,182,174,109,105,93,31,181, -85,87,119,171,187,186,186,91,171,246,211,221,118,235,171,171,31,55,251,59,143,201,76,218,44,132,239,253,255,207,255,252,207, -185,231,241,63,143,153,36,101,79,4,250,46,88,66,151,94,114,238,181,147,179,244,212,201,205,225,159,190,152,158,247,165,219,39, -126,178,240,215,230,173,135,207,118,18,141,19,209,196,142,197,17,82,255,94,153,67,244,35,146,246,69,224,105,157,40,14,121,194, -67,84,15,249,148,73,116,5,228,81,47,17,178,200,19,32,90,211,68,148,130,124,171,150,232,15,224,109,240,23,240,191,64,171,35,50, -64,8,84,129,40,232,6,23,131,45,96,27,184,18,236,6,123,128,13,174,6,215,128,227,224,102,112,27,184,19,156,2,103,192,89,240,44, -120,5,52,70,137,150,129,171,192,13,224,19,224,155,224,87,128,161,193,49,112,17,184,28,236,5,199,192,3,224,81,240,34,120,5,188, -13,194,13,68,237,96,0,92,9,14,128,235,193,67,224,49,240,29,240,11,240,22,240,55,18,189,7,164,193,213,224,111,193,147,224,183, -160,102,22,234,0,87,129,163,224,195,224,39,96,18,244,96,156,222,3,70,192,94,176,31,56,32,15,222,15,142,129,59,192,73,112,15,120, -0,124,24,124,12,124,2,60,1,158,2,223,2,63,6,63,7,175,130,55,192,159,192,95,129,209,76,20,4,81,16,3,61,96,13,216,1,70,193,213, -224,14,240,17,240,48,120,18,156,3,47,129,87,193,95,128,217,130,62,2,11,52,130,14,48,31,44,2,23,129,77,224,111,128,3,174,5,55, -131,7,192,25,240,37,240,60,248,30,248,103,240,91,240,22,152,4,190,86,162,58,208,9,250,193,101,96,35,216,6,222,7,246,131,195,224, -24,56,1,238,3,31,3,15,131,207,128,47,130,175,130,23,193,207,192,107,224,45,240,103,64,49,244,29,84,130,57,160,31,236,0,123,193, -1,48,14,14,129,163,224,102,112,18,124,12,60,12,62,11,190,1,126,12,126,6,254,29,252,26,252,25,120,219,136,102,131,62,176,18, -108,5,123,193,24,112,193,81,112,2,124,8,60,2,62,7,190,6,190,13,126,14,126,7,244,118,196,5,104,4,237,160,23,44,7,151,129,4,200, -128,2,184,6,220,6,238,2,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,193,118,176,3,92,73,178,31,197,127,33,37,151,98,111,8, -43,125,101,153,190,30,122,165,210,183,212,202,254,51,149,230,251,143,1,118,195,30,41,171,151,235,197,242,13,74,119,149,79,177, -174,90,229,183,84,217,107,149,189,90,233,71,149,61,90,102,143,42,123,141,210,111,132,94,167,244,59,148,189,94,217,107,149,190, -84,233,13,101,58,159,183,123,107,101,57,174,63,168,222,213,92,214,254,150,50,189,181,76,111,47,211,103,151,245,133,207,239,163, -170,126,62,199,93,170,206,121,202,135,207,67,175,210,7,149,206,251,178,89,233,143,67,31,82,250,83,101,122,95,153,206,219,191, -73,233,207,65,223,162,116,62,254,151,43,253,133,50,159,127,173,149,103,67,175,26,255,98,61,175,212,202,152,88,160,218,179,77, -233,175,195,158,80,186,171,250,178,80,189,87,199,76,127,139,184,92,64,223,135,244,96,228,108,33,107,105,159,144,61,148,22,50, -76,31,20,178,155,190,65,60,62,154,40,37,164,244,51,148,159,129,17,43,8,57,159,110,20,50,74,183,40,121,171,144,178,30,3,239,187, -155,100,156,157,22,146,209,167,132,140,211,167,133,172,167,207,10,217,71,95,23,178,248,94,172,121,37,95,0,26,85,209,125,162, -253,109,100,10,233,165,247,10,25,20,210,131,246,152,66,182,210,231,69,185,78,145,230,237,222,169,218,57,34,100,29,237,21,210, -164,81,101,63,40,164,159,142,8,105,208,117,74,30,87,249,39,133,212,233,81,33,91,233,51,106,28,190,76,124,205,180,139,247,132, -176,91,112,25,70,126,82,72,143,240,175,68,250,176,144,243,232,135,34,238,42,232,128,138,191,59,69,236,181,138,114,81,140,203, -30,37,63,64,50,182,239,23,114,1,125,77,200,74,122,70,72,75,200,122,172,168,221,66,70,104,76,72,89,174,30,35,37,165,44,95,175, -252,27,212,123,26,176,202,118,11,25,166,239,18,223,11,103,11,123,35,242,159,36,190,158,90,201,17,210,71,87,11,217,72,239,87,233, -163,66,6,232,90,146,235,238,152,144,81,186,94,200,24,125,81,200,110,122,92,201,47,41,251,87,132,236,160,39,132,156,67,95,37, -190,86,229,120,53,99,87,149,178,158,206,8,41,219,213,138,113,191,73,200,32,125,148,248,222,28,162,9,226,251,115,144,174,81,242, -6,226,235,121,22,185,196,215,114,3,221,75,124,29,55,211,211,196,247,234,22,202,8,217,68,31,34,190,166,123,233,125,66,6,69,61, -243,213,120,204,199,143,76,119,211,23,136,239,233,210,206,229,3,66,206,167,23,85,250,37,146,119,52,34,185,182,248,30,81,5,249, -4,54,174,196,28,105,247,148,229,247,169,252,151,145,63,166,242,121,60,51,42,237,151,60,255,109,228,223,164,242,121,253,255,132, -131,231,103,224,213,14,233,251,39,37,249,97,164,65,248,33,173,57,210,214,168,100,183,146,171,148,28,156,195,235,210,133,254,32, -54,61,31,228,48,18,187,53,70,227,214,44,17,161,60,151,215,247,112,187,220,35,219,89,144,182,199,24,206,41,139,18,49,162,131, -150,87,68,184,99,245,9,57,30,171,70,137,42,54,61,111,129,144,93,111,241,182,49,241,190,207,182,203,126,58,22,183,4,69,31,77, -252,240,188,199,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,165,118,121,30,38, -250,60,168,139,239,180,1,156,159,181,148,88,168,83,51,75,244,153,104,169,143,184,140,68,114,177,245,136,145,22,38,125,47,23,165, -52,228,123,133,116,172,94,97,49,148,197,128,165,118,170,230,45,98,52,66,40,61,23,207,146,173,235,143,72,45,148,41,38,78,8, -194,234,213,1,151,76,157,93,186,56,231,121,207,151,8,169,163,237,23,92,23,186,112,57,86,102,128,248,40,189,218,46,247,110,222, -103,31,70,185,7,218,38,120,14,31,247,35,85,43,236,60,197,243,230,35,181,81,164,42,176,47,241,155,64,144,29,180,46,227,227,200, -134,143,7,104,247,109,149,52,124,123,132,134,239,8,210,206,15,212,209,240,7,107,104,248,206,106,138,252,247,206,227,81,188,217, -162,157,199,44,188,165,138,134,143,133,104,251,13,149,148,184,49,66,137,155,130,180,21,87,255,196,45,33,242,30,247,222,125, -200,235,87,53,122,209,115,77,221,8,170,103,203,248,75,96,110,171,68,108,122,69,140,52,194,158,231,241,225,107,70,12,44,195,58, -118,172,1,188,35,232,107,245,53,145,247,198,86,79,19,69,124,78,236,66,156,4,78,108,49,253,30,207,139,240,108,244,189,7,90,43, -206,129,160,22,209,219,186,150,220,16,167,117,62,77,111,244,235,176,55,211,41,10,152,203,205,118,97,115,112,57,246,81,192,183, -228,230,70,145,142,248,29,92,17,79,153,65,198,83,47,251,77,230,196,98,40,17,244,114,79,47,60,151,251,48,139,125,23,83,141,247, -101,93,103,93,63,72,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,63,70,42, -138,216,170,242,132,68,84,120,208,171,16,53,250,253,232,89,13,234,159,235,147,119,156,43,249,140,178,89,251,170,113,46,4,160,31, -153,45,239,23,253,225,90,138,132,219,81,131,247,227,236,11,222,115,222,31,177,95,121,255,232,243,90,164,249,170,200,231,143,208, -161,128,41,198,238,226,240,127,77,86,135,251,10,93,175,133,88,132,186,222,150,99,91,37,235,109,172,16,49,30,166,7,103,243, -25,198,216,251,78,178,72,77,68,79,60,236,167,102,35,113,186,2,173,27,132,71,64,91,138,245,209,98,176,69,149,90,36,50,30,11,226, -28,9,106,195,167,195,148,56,93,67,17,51,241,136,23,158,139,248,136,120,7,189,154,41,173,107,52,205,92,124,253,63,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,71,57,235,18,232,65,214,202,218,160,95,202,35, -5,246,55,39,115,214,42,165,191,54,153,120,164,26,35,215,128,104,237,194,188,21,189,90,181,40,230,160,27,187,110,80,107,244,121, -132,126,61,226,164,209,175,81,177,116,171,86,67,7,99,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,90,13,178,14,171,81,140,225,206,211,81, -234,186,51,36,198,177,155,118,79,134,212,88,44,18,185,75,174,111,147,163,233,73,124,82,90,49,18,230,160,169,121,164,87,47,188, -102,206,151,245,45,158,156,121,46,186,105,217,100,72,107,49,230,106,33,45,113,230,221,30,30,115,238,59,230,180,155,226,40,209, -77,61,147,168,121,85,55,121,39,249,254,225,199,174,84,45,246,80,175,184,7,243,216,233,16,123,86,181,248,172,113,29,210,95,20, -123,79,72,156,177,252,204,255,190,242,251,23,101,255,55,149,254,79,149,126,83,236,218,140,254,42,234,173,38,31,147,118,227,186, -208,23,216,235,236,186,206,175,176,55,24,189,206,232,13,118,109,231,77,154,48,122,49,14,252,76,249,205,108,249,121,39,162,37, -182,249,168,89,79,92,33,163,149,81,128,45,101,136,86,221,219,134,83,226,138,0,181,99,79,31,239,99,148,48,115,177,181,248,140, -131,29,231,138,90,228,39,240,204,89,27,196,30,212,202,194,212,245,219,16,107,209,231,50,236,78,109,93,255,33,158,191,144,103, -105,3,124,248,186,243,225,189,115,213,62,171,81,167,86,221,61,167,167,6,186,23,173,15,97,48,248,157,161,26,31,110,34,161,67,172, -2,253,9,88,23,91,134,176,84,90,47,135,66,204,7,219,86,102,209,133,205,255,51,201,245,4,195,124,232,107,170,152,94,205,122,25, -191,93,46,178,106,168,154,45,80,58,198,150,197,149,14,127,118,129,208,47,176,126,170,74,87,83,163,190,1,209,216,129,27,118,128, -189,28,102,129,146,29,177,220,55,135,22,134,3,149,23,189,241,172,242,231,243,126,129,232,175,99,45,231,210,95,244,119,250,230, -210,47,43,131,21,197,221,245,20,181,7,161,245,245,211,29,21,220,55,136,216,88,116,247,71,167,222,27,209,133,230,65,73,171,19, -99,20,240,56,125,179,105,161,71,88,17,43,62,92,45,155,35,210,187,124,94,42,197,104,180,68,186,105,98,18,163,29,193,104,7,184, -165,155,198,203,218,216,11,111,158,218,174,87,146,124,79,68,74,35,40,228,86,179,78,166,189,33,196,76,53,21,79,18,110,27,70,13, -188,254,149,147,178,102,174,175,154,36,113,63,159,139,150,222,172,246,191,7,196,60,122,232,35,144,94,104,252,222,199,119,255, -103,85,90,167,251,176,69,158,98,215,205,122,158,221,199,72,221,98,136,110,239,144,123,232,182,88,149,248,28,91,180,223,213,33, -99,101,107,44,138,59,187,53,117,158,61,208,33,239,75,17,236,232,126,120,123,248,59,59,228,103,67,156,137,123,176,211,106,137, -100,152,150,50,63,113,25,209,157,149,117,116,240,134,0,181,104,203,209,158,109,123,170,232,184,231,201,85,187,32,43,9,99,166, -97,204,88,215,239,24,201,187,149,71,173,161,70,21,151,179,166,206,255,6,22,106,226,49,203,16,157,26,125,185,67,126,119,208,142, -18,137,131,184,127,228,176,23,231,241,78,23,183,151,66,45,29,50,121,77,137,156,104,207,193,10,138,84,59,177,33,196,85,132,13, -195,187,69,107,55,27,17,25,243,81,151,99,245,16,191,33,201,182,68,196,141,136,137,239,39,152,248,153,131,55,200,27,234,139,101, -125,231,223,37,242,127,26,149,164,27,144,99,121,20,242,198,128,188,51,22,191,23,224,231,215,201,128,28,211,83,144,15,169,252, -242,187,47,207,55,85,61,126,37,249,247,7,103,149,111,135,170,175,78,201,168,122,111,81,198,85,125,113,85,167,159,228,103,157, -184,240,232,19,159,95,162,170,236,130,178,182,149,247,193,162,26,97,55,84,155,75,229,163,194,62,79,249,241,207,160,140,228,189, -83,182,33,42,202,52,193,210,131,222,116,43,123,179,42,23,167,226,120,48,12,111,23,225,194,198,150,145,182,44,70,108,128,204,129, -180,147,118,87,144,182,162,159,60,43,250,187,119,144,181,58,235,56,246,136,155,206,58,49,59,151,203,230,40,12,139,107,59,110, -239,144,237,236,115,71,169,118,77,58,63,50,229,180,181,224,56,201,61,25,155,216,6,210,54,12,145,190,97,104,3,121,240,192,126, -184,145,170,55,22,70,236,245,137,196,150,109,110,206,78,142,45,216,159,60,148,36,54,68,26,156,116,238,163,13,161,200,16,30,222, -161,161,93,67,67,168,32,160,20,174,107,67,187,168,126,40,233,164,114,217,116,42,238,218,19,110,60,129,199,118,55,157,201,247, -83,108,104,36,59,22,207,141,229,51,241,253,120,77,124,250,187,58,22,246,211,226,243,123,204,216,145,126,106,57,111,169,126,106, -31,74,37,51,135,210,7,226,73,199,201,186,73,94,56,190,214,25,201,100,243,105,103,223,234,76,50,143,182,205,62,159,207,38,219, -29,205,166,248,139,222,237,180,1,237,201,169,74,218,102,200,223,100,143,237,81,14,54,92,154,103,112,217,150,222,231,36,221,66, -14,93,105,156,33,59,49,154,203,30,22,69,249,108,196,211,217,248,170,194,222,189,118,206,78,109,112,198,11,110,177,151,181,83, -217,27,54,175,157,24,177,199,121,225,105,230,114,239,186,41,243,230,130,91,102,175,151,246,76,210,217,23,95,61,154,204,109,179, -15,22,108,103,196,158,170,72,228,148,213,95,93,102,222,128,184,219,103,231,248,76,79,55,230,114,133,113,215,78,149,21,171,41, -247,128,131,156,201,72,153,117,243,158,253,152,232,233,158,165,57,47,247,68,219,49,73,211,219,46,109,114,160,250,169,97,134,156, -116,38,197,179,202,43,194,72,219,201,212,244,174,138,209,151,239,108,146,102,199,118,227,235,93,119,124,251,214,161,210,218, -235,167,112,41,23,57,83,173,81,233,114,79,213,163,2,86,133,236,187,155,69,67,172,50,235,80,58,239,78,53,67,88,54,37,199,59,214, -58,110,238,72,63,109,154,201,60,240,238,241,120,71,125,51,120,172,192,15,85,78,175,110,186,97,155,237,242,176,47,25,176,254,70, -10,185,28,182,151,248,234,100,38,35,118,146,214,243,231,247,83,207,255,231,128,192,130,15,31,149,178,208,232,158,217,123,237, -132,61,82,120,135,107,231,249,92,179,8,222,220,161,52,143,221,216,249,253,242,83,115,251,78,143,117,5,190,46,139,75,111,230,220, -126,26,56,95,246,192,121,151,45,38,160,99,230,210,50,24,215,37,71,208,64,204,250,252,153,189,16,59,99,233,145,248,165,66,172, -202,102,51,118,18,227,50,111,102,231,76,118,228,64,62,190,213,134,158,75,58,238,16,146,253,228,135,16,83,176,140,216,14,210,118, -96,143,223,129,61,126,7,246,120,19,15,190,215,35,177,139,2,59,202,246,249,29,187,136,237,34,109,215,70,0,57,188,10,108,160, -234,225,25,86,165,182,219,161,64,114,100,196,206,231,59,250,250,250,168,66,234,235,50,201,125,121,242,38,83,169,28,82,100,38, -199,199,109,39,69,222,61,201,188,189,61,151,33,115,143,24,45,242,140,32,140,200,28,17,177,66,6,223,145,109,242,99,195,31,79,230, -236,68,150,188,234,68,160,64,233,104,160,186,146,158,200,150,78,13,178,70,48,166,174,93,90,140,69,139,156,42,62,32,69,75,233,0, -41,90,100,74,248,84,170,17,45,14,29,153,41,123,36,155,178,169,38,101,239,77,22,50,238,180,201,227,185,25,219,181,41,144,42, -53,165,54,53,227,169,92,61,205,44,171,33,127,42,171,154,76,204,38,131,79,223,17,242,9,129,5,10,77,197,49,25,123,211,118,38,5, -145,41,228,71,73,223,135,204,26,60,138,11,13,47,80,93,10,195,186,150,223,22,84,186,18,233,245,120,149,157,91,199,107,200,11,135, -178,56,37,19,233,65,251,136,112,44,63,49,168,2,134,45,56,37,197,48,240,220,173,118,126,60,235,228,49,200,24,15,94,77,2,7,89,70, -221,69,120,53,216,13,201,7,185,35,153,41,216,100,141,38,243,171,16,145,170,141,54,66,0,150,203,113,107,160,138,81,209,162,161, -180,99,35,82,100,34,79,65,165,36,178,219,17,9,225,81,236,196,91,249,33,149,119,87,143,165,168,122,122,90,58,5,184,81,181,151, -165,201,155,118,82,246,196,230,189,84,145,46,235,161,47,237,168,38,85,164,243,107,39,70,147,133,188,203,91,147,206,139,113,32, -51,157,71,63,93,158,203,165,172,217,151,86,251,55,121,246,103,211,136,131,140,236,167,135,47,53,242,56,201,49,76,169,99,31,94, -157,28,25,181,83,114,58,183,96,149,82,136,27,75,97,88,129,100,41,152,144,192,43,132,228,75,193,227,240,209,208,157,2,26,137, -199,170,35,46,198,163,6,218,186,108,38,147,61,108,167,182,218,169,116,14,53,73,235,84,42,145,149,14,212,48,147,85,141,95,22, -203,174,172,33,122,22,93,213,199,241,118,15,30,11,197,115,17,249,198,139,51,204,53,119,77,210,77,82,168,168,201,138,60,34,82,189, -57,68,58,86,48,85,230,84,24,172,87,211,86,243,14,131,44,101,194,138,229,66,122,174,128,119,231,17,175,85,120,168,246,36,210,99, -54,111,78,5,76,107,178,50,234,168,49,207,3,51,239,242,112,145,29,41,245,158,59,78,197,98,56,207,99,49,153,42,214,98,137,180, -8,12,121,159,163,72,201,178,37,135,113,200,185,71,200,200,143,103,210,46,132,155,204,97,206,33,220,66,158,2,82,138,144,174,44, -233,178,15,193,188,8,32,117,84,152,249,194,158,49,212,224,135,204,139,45,144,66,238,104,249,150,83,205,147,239,220,117,74,198, -178,109,38,12,99,249,34,172,68,122,91,249,203,60,238,104,26,113,194,159,29,125,228,119,209,85,12,211,38,44,223,41,85,197,169, -155,149,251,49,85,184,101,203,177,142,159,3,249,189,217,220,152,157,186,188,44,2,189,216,89,68,11,204,130,35,34,185,226,80,50, -211,81,92,9,198,33,177,76,140,195,57,68,63,105,19,125,116,55,211,190,205,200,27,166,251,249,115,114,217,0,44,60,249,184,102, -156,99,223,100,207,179,231,240,137,219,248,7,54,111,157,55,60,208,203,255,209,211,26,28,78,44,31,76,183,238,30,28,30,188,106,88, -27,111,94,219,118,142,122,252,187,239,101,143,178,91,217,119,217,55,216,99,236,12,187,157,121,195,218,95,180,254,137,137,35,218, -245,215,156,17,133,123,7,6,135,88,164,146,58,217,143,144,75,59,205,221,247,176,191,99,39,216,119,132,247,43,186,231,195,172, -121,112,224,146,3,186,118,11,91,196,152,174,127,128,177,229,39,116,243,17,198,62,57,161,179,195,245,39,116,239,15,89,125,90,59, -212,111,48,195,171,133,251,13,115,120,193,208,130,65,221,248,36,179,6,46,49,140,22,67,51,244,86,157,242,77,3,180,210,63,85, -253,223,179,207,177,79,176,219,240,146,222,57,172,42,138,54,247,106,21,253,109,244,54,122,187,113,249,45,90,111,143,182,179,89, -171,56,252,196,196,181,172,209,242,48,183,189,255,57,214,80,201,234,67,43,126,201,234,171,13,198,171,12,227,216,52,136,107, -115,79,60,126,213,247,250,180,150,31,24,164,237,233,63,215,113,191,102,245,211,29,140,221,205,188,205,168,173,182,71,27,107,254, -105,219,93,43,209,5,202,215,180,182,24,36,180,206,86,250,52,31,222,94,221,250,60,139,183,233,161,211,140,13,245,106,207,80, -143,97,105,151,54,155,214,173,71,244,170,175,51,198,234,171,206,233,225,179,232,180,94,249,41,198,22,156,211,253,7,234,111,215, -43,30,100,245,187,116,223,85,45,187,244,224,71,89,203,144,174,189,200,234,227,204,178,142,60,172,133,118,26,214,74,244,221, -103,4,91,245,64,190,73,115,54,26,1,83,51,125,102,208,180,222,207,90,106,47,106,53,3,116,171,206,39,247,131,226,121,143,120,254, -94,211,222,251,113,12,75,155,78,15,97,172,7,39,218,123,247,107,19,205,3,244,75,62,205,127,208,105,220,88,190,127,160,117,151, -110,30,156,53,208,96,152,116,74,103,31,225,115,167,121,217,15,88,99,80,243,105,187,60,116,138,181,104,1,164,155,140,104,111,116, -121,212,136,118,68,189,90,5,55,96,4,139,74,149,102,66,169,55,68,17,223,41,22,173,144,101,60,209,213,170,132,72,250,163,44, -186,56,218,25,93,143,170,100,134,79,243,203,26,60,197,170,58,73,99,26,157,140,49,252,63,118,204,243,88,173,198,190,95,203,158, -137,61,84,71,6,99,26,50,197,15,121,142,31,243,60,85,103,176,223,212,81,149,233,245,106,44,34,126,68,30,211,166,253,136,180,9, -255,199,234,217,137,216,203,13,90,228,205,6,214,240,251,89,44,242,66,19,139,28,107,247,85,157,156,109,84,189,221,201,170,78, -206,101,85,103,193,83,224,177,46,86,117,166,91,254,125,4,149,125,223,192,101,241,239,183,248,119,4,197,191,225,42,126,119,193, -255,142,139,127,79,82,252,91,46,254,189,66,241,239,185,76,42,253,77,151,110,201,223,193,241,239,98,88,76,254,254,247,49,254,29, -74,76,250,240,223,33,50,171,244,123,69,45,38,223,203,255,6,76,87,254,252,247,120,158,24,137,223,63,241,223,17,146,42,43,126, -247,104,201,182,242,191,55,251,63,108,101,104,241,168,38,0,0,0,0}; - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "()V") \ - METHOD (toString, "toString", "()Ljava/lang/String;") \ - -DECLARE_JNI_CLASS (StringBuffer, "java/lang/StringBuffer") -#undef JNI_CLASS_MEMBERS - -//============================================================================== -#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/rmsl/juce/JuceHTTPStream;") \ - METHOD (connect, "connect", "()Z") \ - METHOD (release, "release", "()V") \ - METHOD (read, "read", "([BI)I") \ - METHOD (getPosition, "getPosition", "()J") \ - METHOD (getTotalLength, "getTotalLength", "()J") \ - METHOD (isExhausted, "isExhausted", "()Z") \ - METHOD (setPosition, "setPosition", "(J)Z") \ - -DECLARE_JNI_CLASS_WITH_BYTECODE (HTTPStream, "com/rmsl/juce/JuceHTTPStream", 16, javaJuceHttpStream, sizeof(javaJuceHttpStream)) -#undef JNI_CLASS_MEMBERS - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (close, "close", "()V") \ - METHOD (read, "read", "([BII)I") \ - -DECLARE_JNI_CLASS (AndroidInputStream, "java/io/InputStream") -#undef JNI_CLASS_MEMBERS - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (acquire, "acquire", "()V") \ - METHOD (release, "release", "()V") \ - -DECLARE_JNI_CLASS (AndroidMulticastLock, "android/net/wifi/WifiManager$MulticastLock") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (createMulticastLock, "createMulticastLock", "(Ljava/lang/String;)Landroid/net/wifi/WifiManager$MulticastLock;") \ - -DECLARE_JNI_CLASS (AndroidWifiManager, "android/net/wifi/WifiManager") -#undef JNI_CLASS_MEMBERS - -static LocalRef getMulticastLock() -{ - static LocalRef multicastLock; - static bool hasChecked = false; - - if (! hasChecked) - { - hasChecked = true; - - auto* env = getEnv(); - - LocalRef wifiManager (env->CallObjectMethod (getAppContext().get(), - AndroidContext.getSystemService, - javaString ("wifi").get())); - - if (wifiManager != nullptr) - { - multicastLock = LocalRef (env->CallObjectMethod (wifiManager.get(), - AndroidWifiManager.createMulticastLock, - javaString ("JUCE_MulticastLock").get())); - } - } - - return multicastLock; -} - -JUCE_API void JUCE_CALLTYPE acquireMulticastLock(); -JUCE_API void JUCE_CALLTYPE acquireMulticastLock() -{ - auto multicastLock = getMulticastLock(); - - if (multicastLock != nullptr) - getEnv()->CallVoidMethod (multicastLock.get(), AndroidMulticastLock.acquire); -} - -JUCE_API void JUCE_CALLTYPE releaseMulticastLock(); -JUCE_API void JUCE_CALLTYPE releaseMulticastLock() -{ - auto multicastLock = getMulticastLock(); - - if (multicastLock != nullptr) - getEnv()->CallVoidMethod (multicastLock.get(), AndroidMulticastLock.release); -} - -//============================================================================== -void MACAddress::findAllAddresses (Array& /*result*/) -{ - // TODO -} - - -JUCE_API bool JUCE_CALLTYPE Process::openEmailWithAttachments (const String& /*targetEmailAddress*/, - const String& /*emailSubject*/, - const String& /*bodyText*/, - const StringArray& /*filesToAttach*/) -{ - // TODO - return false; -} - -//============================================================================== -bool URL::isLocalFile() const -{ - if (getScheme() == "file") - return true; - - if (getScheme() == "content") - { - auto file = AndroidContentUriResolver::getLocalFileFromContentUri (*this); - return (file != File()); - } - - return false; -} - -File URL::getLocalFile() const -{ - if (getScheme() == "content") - { - auto path = AndroidContentUriResolver::getLocalFileFromContentUri (*this); - - // This URL does not refer to a local file - // Call URL::isLocalFile to first check if the URL - // refers to a local file. - jassert (path != File()); - - return path; - } - - return fileFromFileSchemeURL (*this); -} - -String URL::getFileName() const -{ - if (getScheme() == "content") - return AndroidContentUriResolver::getFileNameFromContentUri (*this); - - return toString (false).fromLastOccurrenceOf ("/", false, true); -} - -//============================================================================== -class WebInputStream::Pimpl -{ -public: - enum { contentStreamCacheSize = 1024 }; - - Pimpl (WebInputStream&, const URL& urlToCopy, bool addParametersToBody) - : url (urlToCopy), - isContentURL (urlToCopy.getScheme() == "content"), - addParametersToRequestBody (addParametersToBody), - hasBodyDataToSend (addParametersToRequestBody || url.hasBodyDataToSend()), - httpRequest (hasBodyDataToSend ? "POST" : "GET") - { - } - - ~Pimpl() - { - cancel(); - } - - void cancel() - { - if (isContentURL) - { - stream.callVoidMethod (AndroidInputStream.close); - return; - } - - const ScopedLock lock (createStreamLock); - - if (stream != nullptr) - { - stream.callVoidMethod (HTTPStream.release); - stream.clear(); - } - - hasBeenCancelled = true; - } - - bool connect (WebInputStream::Listener* /*listener*/) - { - auto* env = getEnv(); - - if (isContentURL) - { - auto inputStream = AndroidContentUriResolver::getStreamForContentUri (url, true); - - if (inputStream != nullptr) - { - stream = GlobalRef (inputStream); - statusCode = 200; - - return true; - } - } - else - { - String address = url.toString (! addParametersToRequestBody); - - if (! address.contains ("://")) - address = "http://" + address; - - MemoryBlock postData; - - if (hasBodyDataToSend) - WebInputStream::createHeadersAndPostData (url, - headers, - postData, - addParametersToRequestBody); - - jbyteArray postDataArray = nullptr; - - if (! postData.isEmpty()) - { - postDataArray = env->NewByteArray (static_cast (postData.getSize())); - env->SetByteArrayRegion (postDataArray, 0, static_cast (postData.getSize()), (const jbyte*) postData.getData()); - } - - LocalRef responseHeaderBuffer (env->NewObject (StringBuffer, StringBuffer.constructor)); - - // Annoyingly, the android HTTP functions will choke on this call if you try to do it on the message - // thread. You'll need to move your networking code to a background thread to keep it happy.. - jassert (Thread::getCurrentThread() != nullptr); - - jintArray statusCodeArray = env->NewIntArray (1); - jassert (statusCodeArray != nullptr); - - { - const ScopedLock lock (createStreamLock); - - if (! hasBeenCancelled) - stream = GlobalRef (LocalRef (env->CallStaticObjectMethod (HTTPStream, - HTTPStream.createHTTPStream, - javaString (address).get(), - (jboolean) addParametersToRequestBody, - postDataArray, - javaString (headers).get(), - (jint) timeOutMs, - statusCodeArray, - responseHeaderBuffer.get(), - (jint) numRedirectsToFollow, - javaString (httpRequest).get()))); - } - - if (stream != nullptr && ! stream.callBooleanMethod (HTTPStream.connect)) - stream.clear(); - - jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, nullptr); - statusCode = statusCodeElements[0]; - env->ReleaseIntArrayElements (statusCodeArray, statusCodeElements, 0); - env->DeleteLocalRef (statusCodeArray); - - if (postDataArray != nullptr) - env->DeleteLocalRef (postDataArray); - - if (stream != nullptr) - { - StringArray headerLines; - - { - LocalRef headersString ((jstring) env->CallObjectMethod (responseHeaderBuffer.get(), - StringBuffer.toString)); - headerLines.addLines (juceString (env, headersString)); - } - - for (int i = 0; i < headerLines.size(); ++i) - { - const String& header = headerLines[i]; - const String key (header.upToFirstOccurrenceOf (": ", false, false)); - const String value (header.fromFirstOccurrenceOf (": ", false, false)); - const String previousValue (responseHeaders[key]); - - responseHeaders.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); - } - - return true; - } - } - - return false; - } - - //============================================================================== - // WebInputStream methods - void withExtraHeaders (const String& extraHeaders) - { - if (! headers.endsWithChar ('\n') && headers.isNotEmpty()) - headers << "\r\n"; - - headers << extraHeaders; - - if (! headers.endsWithChar ('\n') && headers.isNotEmpty()) - headers << "\r\n"; - } - - void withCustomRequestCommand (const String& customRequestCommand) { httpRequest = customRequestCommand; } - void withConnectionTimeout (int timeoutInMs) { timeOutMs = timeoutInMs; } - void withNumRedirectsToFollow (int maxRedirectsToFollow) { numRedirectsToFollow = maxRedirectsToFollow; } - StringPairArray getRequestHeaders() const { return WebInputStream::parseHttpHeaders (headers); } - StringPairArray getResponseHeaders() const { return responseHeaders; } - int getStatusCode() const { return statusCode; } - - //============================================================================== - bool isError() const { return stream == nullptr; } - bool isExhausted() { return (isContentURL ? eofStreamReached : stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted)); } - int64 getTotalLength() { return (isContentURL ? -1 : (stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0)); } - int64 getPosition() { return (isContentURL ? readPosition : (stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0)); } - - //============================================================================== - bool setPosition (int64 wantedPos) - { - if (isContentURL) - { - if (wantedPos < readPosition) - return false; - - auto bytesToSkip = wantedPos - readPosition; - - if (bytesToSkip == 0) - return true; - - HeapBlock buffer (bytesToSkip); - - return (read (buffer.getData(), (int) bytesToSkip) > 0); - } - - return stream != nullptr && stream.callBooleanMethod (HTTPStream.setPosition, (jlong) wantedPos); - } - - int read (void* buffer, int bytesToRead) - { - jassert (buffer != nullptr && bytesToRead >= 0); - - const ScopedLock lock (createStreamLock); - - if (stream == nullptr) - return 0; - - JNIEnv* env = getEnv(); - - jbyteArray javaArray = env->NewByteArray (bytesToRead); - - auto numBytes = (isContentURL ? stream.callIntMethod (AndroidInputStream.read, javaArray, 0, (jint) bytesToRead) - : stream.callIntMethod (HTTPStream.read, javaArray, (jint) bytesToRead)); - - if (numBytes > 0) - env->GetByteArrayRegion (javaArray, 0, numBytes, static_cast (buffer)); - - env->DeleteLocalRef (javaArray); - - readPosition += jmax (0, numBytes); - - if (numBytes == -1) - eofStreamReached = true; - - return numBytes; - } - - //============================================================================== - int statusCode = 0; - -private: - const URL url; - const bool isContentURL, addParametersToRequestBody, hasBodyDataToSend; - bool eofStreamReached = false; - int numRedirectsToFollow = 5, timeOutMs = 0; - String httpRequest, headers; - StringPairArray responseHeaders; - CriticalSection createStreamLock; - bool hasBeenCancelled = false; - int readPosition = 0; - - GlobalRef stream; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) -}; - -std::unique_ptr URL::downloadToFile (const File& targetLocation, const DownloadTaskOptions& options) -{ - return URL::DownloadTask::createFallbackDownloader (*this, targetLocation, options); -} - -//============================================================================== -#if __ANDROID_API__ < 24 // Android support for getifadds was added in Android 7.0 (API 24) so the posix implementation does not apply - -static IPAddress makeAddress (const sockaddr_in *addr_in) -{ - if (addr_in->sin_addr.s_addr == INADDR_NONE) - return {}; - - return IPAddress (ntohl (addr_in->sin_addr.s_addr)); -} - -struct InterfaceInfo -{ - IPAddress interfaceAddress, broadcastAddress; -}; - -static Array findIPAddresses (int dummySocket) -{ - ifconf cfg; - HeapBlock buffer; - int bufferSize = 1024; - - do - { - bufferSize *= 2; - buffer.calloc (bufferSize); - - cfg.ifc_len = bufferSize; - cfg.ifc_buf = buffer; - - if (ioctl (dummySocket, SIOCGIFCONF, &cfg) < 0 && errno != EINVAL) - return {}; - - } while (bufferSize < cfg.ifc_len + 2 * (int) (IFNAMSIZ + sizeof (struct sockaddr_in6))); - - Array result; - - for (size_t i = 0; i < (size_t) cfg.ifc_len / (size_t) sizeof (struct ifreq); ++i) - { - auto& item = cfg.ifc_req[i]; - - if (item.ifr_addr.sa_family == AF_INET) - { - InterfaceInfo info; - info.interfaceAddress = makeAddress (reinterpret_cast (&item.ifr_addr)); - - if (! info.interfaceAddress.isNull()) - { - if (ioctl (dummySocket, SIOCGIFBRDADDR, &item) == 0) - info.broadcastAddress = makeAddress (reinterpret_cast (&item.ifr_broadaddr)); - - result.add (info); - } - } - else if (item.ifr_addr.sa_family == AF_INET6) - { - // TODO: IPv6 - } - } - - return result; -} - -static Array findIPAddresses() -{ - auto dummySocket = socket (AF_INET, SOCK_DGRAM, 0); // a dummy socket to execute the IO control - - if (dummySocket < 0) - return {}; - - auto result = findIPAddresses (dummySocket); - ::close (dummySocket); - return result; -} - -void IPAddress::findAllAddresses (Array& result, bool /*includeIPv6*/) -{ - for (auto& a : findIPAddresses()) - result.add (a.interfaceAddress); -} - -IPAddress IPAddress::getInterfaceBroadcastAddress (const IPAddress& address) -{ - for (auto& a : findIPAddresses()) - if (a.interfaceAddress == address) - return a.broadcastAddress; - - return {}; -} - -#endif - -} // namespace juce diff --git a/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp b/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp deleted file mode 100644 index 1bbc073b7..000000000 --- a/source/modules/juce_core/native/juce_android_RuntimePermissions.cpp +++ /dev/null @@ -1,261 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 String jucePermissionToAndroidPermission (RuntimePermissions::PermissionID permission) -{ - switch (permission) - { - case RuntimePermissions::recordAudio: return "android.permission.RECORD_AUDIO"; - case RuntimePermissions::bluetoothMidi: return "android.permission.ACCESS_FINE_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"; - } - - // invalid permission - jassertfalse; - return {}; -} - -static RuntimePermissions::PermissionID androidPermissionToJucePermission (const String& permission) -{ - if (permission == "android.permission.RECORD_AUDIO") return RuntimePermissions::recordAudio; - else if (permission == "android.permission.ACCESS_FINE_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() override = default; - - 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 != nullptr); - - 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) -{ - 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 - // the Projucer. Otherwise this can't work. - jassertfalse; - - callback (false); - return; - } - - auto alreadyGranted = isGranted (permission); - - if (alreadyGranted || getAndroidSDKVersion() < 23) - { - callback (alreadyGranted); - return; - } - - 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 getAndroidSDKVersion() >= 23; -} - -bool RuntimePermissions::isGranted (PermissionID 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/source/modules/juce_core/native/juce_android_SystemStats.cpp b/source/modules/juce_core/native/juce_android_SystemStats.cpp deleted file mode 100644 index 8c3e416df..000000000 --- a/source/modules/juce_core/native/juce_android_SystemStats.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -//============================================================================== -namespace AndroidStatsHelpers -{ - #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 String getSystemProperty (const String& name) - { - return juceString (LocalRef ((jstring) getEnv()->CallStaticObjectMethod (SystemClass, - SystemClass.getProperty, - javaString (name).get()))); - } - - static String getLocaleValue (bool isRegion) - { - auto* env = getEnv(); - LocalRef locale (env->CallStaticObjectMethod (JavaLocale, JavaLocale.getDefault)); - - auto stringResult = isRegion ? env->CallObjectMethod (locale.get(), JavaLocale.getCountry) - : env->CallObjectMethod (locale.get(), JavaLocale.getLanguage); - - return juceString (LocalRef ((jstring) stringResult)); - } - - static String getAndroidOsBuildValue (const char* fieldName) - { - return juceString (LocalRef ((jstring) getEnv()->GetStaticObjectField ( - AndroidBuild, getEnv()->GetStaticFieldID (AndroidBuild, fieldName, "Ljava/lang/String;")))); - } -} - -//============================================================================== -SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() -{ - return Android; -} - -String SystemStats::getOperatingSystemName() -{ - return "Android " + AndroidStatsHelpers::getSystemProperty ("os.version"); -} - -String SystemStats::getDeviceDescription() -{ - return AndroidStatsHelpers::getAndroidOsBuildValue ("MODEL") - + "-" + AndroidStatsHelpers::getAndroidOsBuildValue ("SERIAL"); -} - -String SystemStats::getDeviceManufacturer() -{ - return AndroidStatsHelpers::getAndroidOsBuildValue ("MANUFACTURER"); -} - -bool SystemStats::isOperatingSystem64Bit() -{ - #if JUCE_64BIT - return true; - #else - return false; - #endif -} - -String SystemStats::getCpuVendor() -{ - return AndroidStatsHelpers::getSystemProperty ("os.arch"); -} - -String SystemStats::getCpuModel() -{ - return readPosixConfigFileValue ("/proc/cpuinfo", "Hardware"); -} - -int SystemStats::getCpuSpeedInMegahertz() -{ - int maxFreqKHz = 0; - - for (int i = 0; i < getNumCpus(); ++i) - { - int freqKHz = File ("/sys/devices/system/cpu/cpu" + String(i) + "/cpufreq/cpuinfo_max_freq") - .loadFileAsString() - .getIntValue(); - - maxFreqKHz = jmax (freqKHz, maxFreqKHz); - } - - return maxFreqKHz / 1000; -} - -int SystemStats::getMemorySizeInMegabytes() -{ - #if __ANDROID_API__ >= 9 - struct sysinfo sysi; - - if (sysinfo (&sysi) == 0) - return static_cast ((sysi.totalram * sysi.mem_unit) / (1024 * 1024)); - #endif - - return 0; -} - -int SystemStats::getPageSize() -{ - return static_cast (sysconf (_SC_PAGESIZE)); -} - -//============================================================================== -String SystemStats::getLogonName() -{ - if (const char* user = getenv ("USER")) - return CharPointer_UTF8 (user); - - if (struct passwd* const pw = getpwuid (getuid())) - return CharPointer_UTF8 (pw->pw_name); - - return {}; -} - -String SystemStats::getFullUserName() -{ - return getLogonName(); -} - -String SystemStats::getComputerName() -{ - char name [256] = { 0 }; - if (gethostname (name, sizeof (name) - 1) == 0) - return name; - - return {}; -} - - -String SystemStats::getUserLanguage() { return AndroidStatsHelpers::getLocaleValue (false); } -String SystemStats::getUserRegion() { return AndroidStatsHelpers::getLocaleValue (true); } -String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } - -//============================================================================== -void CPUInformation::initialise() noexcept -{ - numPhysicalCPUs = numLogicalCPUs = jmax ((int) 1, (int) android_getCpuCount()); - - auto cpuFamily = android_getCpuFamily(); - auto cpuFeatures = android_getCpuFeatures(); - - if (cpuFamily == ANDROID_CPU_FAMILY_X86 || cpuFamily == ANDROID_CPU_FAMILY_X86_64) - { - hasMMX = hasSSE = hasSSE2 = (cpuFamily == ANDROID_CPU_FAMILY_X86_64); - - hasSSSE3 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_SSSE3) != 0); - hasSSE41 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_SSE4_1) != 0); - hasSSE42 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_SSE4_2) != 0); - hasAVX = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_AVX) != 0); - hasAVX2 = ((cpuFeatures & ANDROID_CPU_X86_FEATURE_AVX2) != 0); - - // Google does not distinguish between MMX, SSE, SSE2, SSE3 and SSSE3. So - // I assume (and quick Google searches seem to confirm this) that there are - // only devices out there that either support all of this or none of this. - if (hasSSSE3) - hasMMX = hasSSE = hasSSE2 = hasSSE3 = true; - } - else if (cpuFamily == ANDROID_CPU_FAMILY_ARM) - { - hasNeon = ((cpuFeatures & ANDROID_CPU_ARM_FEATURE_NEON) != 0); - } - else if (cpuFamily == ANDROID_CPU_FAMILY_ARM64) - { - // all arm 64-bit cpus have neon - hasNeon = true; - } -} - -//============================================================================== -uint32 juce_millisecondsSinceStartup() noexcept -{ - timespec t; - clock_gettime (CLOCK_MONOTONIC, &t); - - return static_cast (t.tv_sec) * 1000U + static_cast (t.tv_nsec) / 1000000U; -} - -int64 Time::getHighResolutionTicks() noexcept -{ - timespec t; - clock_gettime (CLOCK_MONOTONIC, &t); - - return (t.tv_sec * (int64) 1000000) + (t.tv_nsec / 1000); -} - -int64 Time::getHighResolutionTicksPerSecond() noexcept -{ - return 1000000; // (microseconds) -} - -double Time::getMillisecondCounterHiRes() noexcept -{ - return (double) getHighResolutionTicks() * 0.001; -} - -bool Time::setSystemTimeToThisTime() const -{ - jassertfalse; - return false; -} - -} // namespace juce diff --git a/source/modules/juce_core/native/juce_android_Threads.cpp b/source/modules/juce_core/native/juce_android_Threads.cpp deleted file mode 100644 index 71d76de09..000000000 --- a/source/modules/juce_core/native/juce_android_Threads.cpp +++ /dev/null @@ -1,395 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -/* - Note that a lot of methods that you'd expect to find in this file actually - 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; -} - -static 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/rmsl/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.rmsl.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() override - { - 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) -{ - // TODO - - struct sched_param param; - int policy, maxp, minp; - - const int p = (int) prior; - - if (p <= 1) - policy = SCHED_OTHER; - else - policy = SCHED_RR; - - minp = sched_get_priority_min (policy); - maxp = sched_get_priority_max (policy); - - if (p < 2) - param.sched_priority = 0; - else if (p == 2 ) - // Set to middle of lower realtime priority range - param.sched_priority = minp + (maxp - minp) / 4; - else - // Set to middle of higher realtime priority range - param.sched_priority = minp + (3 * (maxp - minp) / 4); - - pthread_setschedparam (pthread_self(), policy, ¶m); -} - -JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger() noexcept -{ - StringArray lines; - File ("/proc/self/status").readLines (lines); - - for (int i = lines.size(); --i >= 0;) // (NB - it's important that this runs in reverse order) - if (lines[i].upToFirstOccurrenceOf (":", false, false).trim().equalsIgnoreCase ("TracerPid")) - return (lines[i].fromFirstOccurrenceOf (":", false, false).trim().getIntValue() > 0); - - return false; -} - -JUCE_API void JUCE_CALLTYPE Process::raisePrivilege() {} -JUCE_API void JUCE_CALLTYPE Process::lowerPrivilege() {} - - - -} // namespace juce diff --git a/source/modules/juce_core/native/juce_mac_ObjCHelpers.h b/source/modules/juce_core/native/juce_mac_ObjCHelpers.h index 363a88167..55cb01a23 100644 --- a/source/modules/juce_core/native/juce_mac_ObjCHelpers.h +++ b/source/modules/juce_core/native/juce_mac_ObjCHelpers.h @@ -502,7 +502,7 @@ public: bool operator!= (const void* ptr) const { return ((const void*) block != ptr); } ~ObjCBlock() { if (block != nullptr) [block release]; } - operator BlockType() { return block; } + operator BlockType() const { return block; } private: BlockType block; diff --git a/source/modules/juce_core/native/juce_posix_SharedCode.h b/source/modules/juce_core/native/juce_posix_SharedCode.h index eb5fc3300..efe1bda73 100644 --- a/source/modules/juce_core/native/juce_posix_SharedCode.h +++ b/source/modules/juce_core/native/juce_posix_SharedCode.h @@ -288,6 +288,12 @@ bool File::hasWriteAccess() const return false; } +bool File::hasReadAccess() const +{ + return fullPath.isNotEmpty() + && access (fullPath.toUTF8(), R_OK) == 0; +} + static bool setFileModeFlags (const String& fullPath, mode_t flags, bool shouldSet) noexcept { juce_statStruct info; diff --git a/source/modules/juce_core/native/juce_win32_ComSmartPtr.h b/source/modules/juce_core/native/juce_win32_ComSmartPtr.h index ff4e9d480..ce5f14159 100644 --- a/source/modules/juce_core/native/juce_win32_ComSmartPtr.h +++ b/source/modules/juce_core/native/juce_win32_ComSmartPtr.h @@ -23,7 +23,7 @@ namespace juce { -#if (! defined (_MSC_VER) && ! defined (__uuidof)) +#if (JUCE_MINGW && JUCE_32BIT) || (! defined (_MSC_VER) && ! defined (__uuidof)) #ifdef __uuidof #undef __uuidof #endif diff --git a/source/modules/juce_core/native/juce_win32_Files.cpp b/source/modules/juce_core/native/juce_win32_Files.cpp index e09585a4b..7d93f26af 100644 --- a/source/modules/juce_core/native/juce_win32_Files.cpp +++ b/source/modules/juce_core/native/juce_win32_Files.cpp @@ -159,6 +159,83 @@ namespace WindowsFileHelpers return Result::fail (String (messageBuffer)); } + + // The docs for the Windows security API aren't very clear. Some parts of the following + // function (the flags passed to GetNamedSecurityInfo, duplicating the primary access token) + // were guided by the example at https://blog.aaronballman.com/2011/08/how-to-check-access-rights/ + static bool hasFileAccess (const File& file, DWORD accessType) + { + const auto& path = file.getFullPathName(); + + if (path.isEmpty()) + return false; + + struct PsecurityDescriptorGuard + { + ~PsecurityDescriptorGuard() { if (psecurityDescriptor != nullptr) LocalFree (psecurityDescriptor); } + PSECURITY_DESCRIPTOR psecurityDescriptor = nullptr; + }; + + PsecurityDescriptorGuard descriptorGuard; + + if (GetNamedSecurityInfo (path.toWideCharPointer(), + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + nullptr, + nullptr, + nullptr, + nullptr, + &descriptorGuard.psecurityDescriptor) != ERROR_SUCCESS) + { + return false; + } + + struct HandleGuard + { + ~HandleGuard() { if (handle != INVALID_HANDLE_VALUE) CloseHandle (handle); } + HANDLE handle = nullptr; + }; + + HandleGuard primaryTokenGuard; + + if (! OpenProcessToken (GetCurrentProcess(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE | TOKEN_QUERY | STANDARD_RIGHTS_READ, + &primaryTokenGuard.handle)) + { + return false; + } + + HandleGuard duplicatedTokenGuard; + + if (! DuplicateToken (primaryTokenGuard.handle, + SecurityImpersonation, + &duplicatedTokenGuard.handle)) + { + return false; + } + + GENERIC_MAPPING mapping { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS }; + + MapGenericMask (&accessType, &mapping); + DWORD allowed = 0; + BOOL granted = false; + PRIVILEGE_SET set; + DWORD setSize = sizeof (set); + + if (! AccessCheck (descriptorGuard.psecurityDescriptor, + duplicatedTokenGuard.handle, + accessType, + &mapping, + &set, + &setSize, + &allowed, + &granted)) + { + return false; + } + + return granted != FALSE; + } } // namespace WindowsFileHelpers //============================================================================== @@ -199,17 +276,25 @@ bool File::isDirectory() const bool File::hasWriteAccess() const { - if (fullPath.isEmpty()) - return true; + if (exists()) + { + const auto attr = WindowsFileHelpers::getAtts (fullPath); - auto attr = WindowsFileHelpers::getAtts (fullPath); + return WindowsFileHelpers::hasFileAccess (*this, GENERIC_WRITE) + && (attr == INVALID_FILE_ATTRIBUTES + || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 + || (attr & FILE_ATTRIBUTE_READONLY) == 0); + } + + if ((! isDirectory()) && fullPath.containsChar (getSeparatorChar())) + return getParentDirectory().hasWriteAccess(); - // NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for - // folders, and can be incorrectly set for some special folders, so we'll just say - // that folders are always writable. - return attr == INVALID_FILE_ATTRIBUTES - || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 - || (attr & FILE_ATTRIBUTE_READONLY) == 0; + return false; +} + +bool File::hasReadAccess() const +{ + return WindowsFileHelpers::hasFileAccess (*this, GENERIC_READ); } bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const diff --git a/source/modules/juce_core/network/juce_URL.cpp b/source/modules/juce_core/network/juce_URL.cpp index 3846b30bd..ce9c50305 100644 --- a/source/modules/juce_core/network/juce_URL.cpp +++ b/source/modules/juce_core/network/juce_URL.cpp @@ -792,6 +792,11 @@ std::unique_ptr URL::createInputStream (const InputStreamOptions& o std::unique_ptr URL::createOutputStream() const { + #if JUCE_ANDROID + if (auto stream = AndroidDocument::fromDocument (*this).createOutputStream()) + return stream; + #endif + if (isLocalFile()) { #if JUCE_IOS @@ -802,11 +807,7 @@ std::unique_ptr URL::createOutputStream() const #endif } - #if JUCE_ANDROID - return std::unique_ptr (juce_CreateContentURIOutputStream (*this)); - #else return nullptr; - #endif } //============================================================================== diff --git a/source/modules/juce_core/system/juce_CompilerSupport.h b/source/modules/juce_core/system/juce_CompilerSupport.h index ba55ab658..bbe21625b 100644 --- a/source/modules/juce_core/system/juce_CompilerSupport.h +++ b/source/modules/juce_core/system/juce_CompilerSupport.h @@ -72,8 +72,8 @@ // MSVC #if JUCE_MSVC - #if _MSC_FULL_VER < 190024210 // VS2015 - #error "JUCE requires Visual Studio 2015 Update 3 or later" + #if _MSC_FULL_VER < 191025017 // VS2017 + #error "JUCE requires Visual Studio 2017 or later" #endif #ifndef JUCE_EXCEPTIONS_DISABLED diff --git a/source/modules/juce_core/system/juce_StandardHeader.h b/source/modules/juce_core/system/juce_StandardHeader.h index d18e7d9fb..a84c76a4f 100644 --- a/source/modules/juce_core/system/juce_StandardHeader.h +++ b/source/modules/juce_core/system/juce_StandardHeader.h @@ -27,9 +27,9 @@ See also SystemStats::getJUCEVersion() for a string version. */ -#define JUCE_MAJOR_VERSION 6 -#define JUCE_MINOR_VERSION 1 -#define JUCE_BUILDNUMBER 6 +#define JUCE_MAJOR_VERSION 7 +#define JUCE_MINOR_VERSION 0 +#define JUCE_BUILDNUMBER 1 /** Current JUCE version number. @@ -41,6 +41,11 @@ */ #define JUCE_VERSION ((JUCE_MAJOR_VERSION << 16) + (JUCE_MINOR_VERSION << 8) + JUCE_BUILDNUMBER) +#if ! DOXYGEN +#define JUCE_VERSION_ID \ + volatile auto juceVersionId = "juce_version_" JUCE_STRINGIFY(JUCE_MAJOR_VERSION) "_" JUCE_STRINGIFY(JUCE_MINOR_VERSION) "_" JUCE_STRINGIFY(JUCE_BUILDNUMBER); \ + ignoreUnused (juceVersionId); +#endif //============================================================================== #include diff --git a/source/modules/juce_core/threads/juce_ChildProcess.cpp b/source/modules/juce_core/threads/juce_ChildProcess.cpp index 76f13812f..40ad651f0 100644 --- a/source/modules/juce_core/threads/juce_ChildProcess.cpp +++ b/source/modules/juce_core/threads/juce_ChildProcess.cpp @@ -81,7 +81,7 @@ String ChildProcess::readAllProcessOutput() } -uint32 ChildProcess::getPID() const noexcept +int ChildProcess::getPID() const noexcept { return activeProcess != nullptr ? activeProcess->getPID() : 0; } diff --git a/source/modules/juce_core/threads/juce_ChildProcess.h b/source/modules/juce_core/threads/juce_ChildProcess.h index 71892f8e9..f6076fb82 100644 --- a/source/modules/juce_core/threads/juce_ChildProcess.h +++ b/source/modules/juce_core/threads/juce_ChildProcess.h @@ -101,7 +101,7 @@ public: */ bool kill(); - uint32 getPID() const noexcept; + int getPID() const noexcept; private: //============================================================================== diff --git a/source/modules/juce_data_structures/juce_data_structures.h b/source/modules/juce_data_structures/juce_data_structures.h index 3974b0e8e..176392e9a 100644 --- a/source/modules/juce_data_structures/juce_data_structures.h +++ b/source/modules/juce_data_structures/juce_data_structures.h @@ -35,7 +35,7 @@ ID: juce_data_structures vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE data model helper classes description: Classes for undo/redo management, and smart data structures. website: http://www.juce.com/juce diff --git a/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp b/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp index d72f368c6..d7e4b00ad 100644 --- a/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp +++ b/source/modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp @@ -61,6 +61,8 @@ struct ChildProcessPingThread : public Thread, int timeoutMs; + using AsyncUpdater::cancelPendingUpdate; + private: Atomic countdown; @@ -97,6 +99,7 @@ struct ChildProcessCoordinator::Connection : public InterprocessConnection, ~Connection() override { + cancelPendingUpdate(); stopThread (10000); } @@ -206,6 +209,7 @@ struct ChildProcessWorker::Connection : public InterprocessConnection, ~Connection() override { + cancelPendingUpdate(); stopThread (10000); disconnect(); } diff --git a/source/modules/juce_events/juce_events.cpp b/source/modules/juce_events/juce_events.cpp index dc716333e..c6678d5d2 100644 --- a/source/modules/juce_events/juce_events.cpp +++ b/source/modules/juce_events/juce_events.cpp @@ -61,13 +61,13 @@ #include "broadcasters/juce_ActionBroadcaster.cpp" #include "broadcasters/juce_AsyncUpdater.cpp" #include "broadcasters/juce_ChangeBroadcaster.cpp" -// #include "timers/juce_MultiTimer.cpp" +#include "timers/juce_MultiTimer.cpp" #include "timers/juce_Timer.cpp" -// #include "interprocess/juce_InterprocessConnection.cpp" -// #include "interprocess/juce_InterprocessConnectionServer.cpp" -// #include "interprocess/juce_ConnectedChildProcess.cpp" -// #include "interprocess/juce_NetworkServiceDiscovery.cpp" -// #include "native/juce_ScopedLowPowerModeDisabler.cpp" +#include "interprocess/juce_InterprocessConnection.cpp" +#include "interprocess/juce_InterprocessConnectionServer.cpp" +#include "interprocess/juce_ConnectedChildProcess.cpp" +#include "interprocess/juce_NetworkServiceDiscovery.cpp" +#include "native/juce_ScopedLowPowerModeDisabler.cpp" //============================================================================== #if JUCE_MAC || JUCE_IOS diff --git a/source/modules/juce_events/juce_events.h b/source/modules/juce_events/juce_events.h index 88eeabce7..1a78e86fd 100644 --- a/source/modules/juce_events/juce_events.h +++ b/source/modules/juce_events/juce_events.h @@ -32,7 +32,7 @@ ID: juce_events vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE message and event handling classes description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. website: http://www.juce.com/juce diff --git a/source/modules/juce_events/messages/juce_MessageManager.cpp b/source/modules/juce_events/messages/juce_MessageManager.cpp index 11c060a70..153ac6247 100644 --- a/source/modules/juce_events/messages/juce_MessageManager.cpp +++ b/source/modules/juce_events/messages/juce_MessageManager.cpp @@ -26,6 +26,8 @@ namespace juce MessageManager::MessageManager() noexcept : messageThreadId (Thread::getCurrentThreadId()) { + JUCE_VERSION_ID + if (JUCEApplicationBase::isStandaloneApp()) Thread::setCurrentThreadName ("JUCE Message Thread"); } diff --git a/source/modules/juce_events/native/juce_android_Messaging.cpp b/source/modules/juce_events/native/juce_android_Messaging.cpp deleted file mode 100644 index 7cf749f95..000000000 --- a/source/modules/juce_events/native/juce_android_Messaging.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -//============================================================================== -namespace Android -{ - class Runnable : public juce::AndroidInterfaceImplementer - { - public: - virtual void run() = 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 == "run") - { - run(); - return nullptr; - } - - // invoke base class - return AndroidInterfaceImplementer::invoke (proxy, method, args); - } - }; - - struct Handler - { - Handler() : nativeHandler (LocalRef (getEnv()->NewObject (AndroidHandler, AndroidHandler.constructor))) {} - ~Handler() { clearSingletonInstance(); } - - JUCE_DECLARE_SINGLETON (Handler, false) - - bool post (jobject runnable) - { - return (getEnv()->CallBooleanMethod (nativeHandler.get(), AndroidHandler.post, runnable) != 0); - } - - GlobalRef nativeHandler; - }; - - JUCE_IMPLEMENT_SINGLETON (Handler) -} - -//============================================================================== -struct AndroidMessageQueue : private Android::Runnable -{ - JUCE_DECLARE_SINGLETON_SINGLETHREADED (AndroidMessageQueue, true) - - AndroidMessageQueue() - : self (CreateJavaInterface (this, "java/lang/Runnable")) - { - } - - ~AndroidMessageQueue() override - { - JUCE_ASSERT_MESSAGE_THREAD - clearSingletonInstance(); - } - - bool post (MessageManager::MessageBase::Ptr&& message) - { - queue.add (std::move (message)); - - // this will call us on the message thread - return handler.post (self.get()); - } - -private: - - void run() override - { - for (;;) - { - MessageManager::MessageBase::Ptr message (queue.removeAndReturn (0)); - - if (message == nullptr) - break; - - message->messageCallback(); - } - } - - // the this pointer to this class in Java land - GlobalRef self; - - ReferenceCountedArray queue; - Android::Handler handler; -}; - -JUCE_IMPLEMENT_SINGLETON (AndroidMessageQueue) - -//============================================================================== -void MessageManager::doPlatformSpecificInitialisation() { AndroidMessageQueue::getInstance(); } -void MessageManager::doPlatformSpecificShutdown() { AndroidMessageQueue::deleteInstance(); } - -bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) -{ - return AndroidMessageQueue::getInstance()->post (message); -} - -//============================================================================== -void MessageManager::broadcastMessage (const String&) -{ -} - -void MessageManager::runDispatchLoop() -{ -} - -void MessageManager::stopDispatchLoop() -{ - struct QuitCallback : public CallbackMessage - { - QuitCallback() {} - - void messageCallback() override - { - auto* env = getEnv(); - LocalRef activity (getCurrentActivity()); - - if (activity != nullptr) - { - jmethodID quitMethod = env->GetMethodID (AndroidActivity, "finishAndRemoveTask", "()V"); - - if (quitMethod != nullptr) - { - env->CallVoidMethod (activity.get(), quitMethod); - return; - } - - quitMethod = env->GetMethodID (AndroidActivity, "finish", "()V"); - jassert (quitMethod != nullptr); - env->CallVoidMethod (activity.get(), quitMethod); - } - else - { - jassertfalse; - } - } - }; - - (new QuitCallback())->post(); - 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() override - { - 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(); -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/source/modules/juce_events/native/juce_ios_MessageManager.mm b/source/modules/juce_events/native/juce_ios_MessageManager.mm deleted file mode 100644 index 68ea66a89..000000000 --- a/source/modules/juce_events/native/juce_ios_MessageManager.mm +++ /dev/null @@ -1,103 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - 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 -{ - -void MessageManager::runDispatchLoop() -{ - jassert (isThisTheMessageThread()); // must only be called by the message thread - - while (quitMessagePosted.get() == 0) - { - JUCE_AUTORELEASEPOOL - { - [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode - beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]]; - } - } -} - -void MessageManager::stopDispatchLoop() -{ - if (! SystemStats::isRunningInAppExtensionSandbox()) - [[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]]; - - exit (0); // iOS apps get no mercy.. -} - -#if JUCE_MODAL_LOOPS_PERMITTED -bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) -{ - JUCE_AUTORELEASEPOOL - { - jassert (isThisTheMessageThread()); // must only be called by the message thread - - uint32 startTime = Time::getMillisecondCounter(); - NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001]; - - while (quitMessagePosted.get() == 0) - { - JUCE_AUTORELEASEPOOL - { - [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode - beforeDate: endDate]; - - if (millisecondsToRunFor >= 0 - && Time::getMillisecondCounter() >= startTime + (uint32) millisecondsToRunFor) - break; - } - } - - return quitMessagePosted.get() == 0; - } -} -#endif - -//============================================================================== -static std::unique_ptr messageQueue; - -void MessageManager::doPlatformSpecificInitialisation() -{ - if (messageQueue == nullptr) - messageQueue.reset (new MessageQueue()); -} - -void MessageManager::doPlatformSpecificShutdown() -{ - messageQueue = nullptr; -} - -bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) -{ - if (messageQueue != nullptr) - messageQueue->post (message); - - return true; -} - -void MessageManager::broadcastMessage (const String&) -{ - // N/A on current iOS -} - -} // namespace juce diff --git a/source/modules/juce_graphics/juce_graphics.cpp b/source/modules/juce_graphics/juce_graphics.cpp index 4f010fd64..d852554be 100644 --- a/source/modules/juce_graphics/juce_graphics.cpp +++ b/source/modules/juce_graphics/juce_graphics.cpp @@ -116,11 +116,11 @@ #include "geometry/juce_PathStrokeType.cpp" #include "placement/juce_RectanglePlacement.cpp" #include "contexts/juce_GraphicsContext.cpp" -// #include "contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp" +#include "contexts/juce_LowLevelGraphicsPostScriptRenderer.cpp" #include "contexts/juce_LowLevelGraphicsSoftwareRenderer.cpp" #include "images/juce_Image.cpp" -// #include "images/juce_ImageCache.cpp" -// #include "images/juce_ImageConvolutionKernel.cpp" +#include "images/juce_ImageCache.cpp" +#include "images/juce_ImageConvolutionKernel.cpp" #include "images/juce_ImageFileFormat.cpp" #include "image_formats/juce_GIFLoader.cpp" #include "image_formats/juce_JPEGLoader.cpp" @@ -132,7 +132,7 @@ #include "fonts/juce_GlyphArrangement.cpp" #include "fonts/juce_TextLayout.cpp" #include "effects/juce_DropShadowEffect.cpp" -// #include "effects/juce_GlowEffect.cpp" +#include "effects/juce_GlowEffect.cpp" #if JUCE_UNIT_TESTS #include "geometry/juce_Rectangle_test.cpp" diff --git a/source/modules/juce_graphics/juce_graphics.h b/source/modules/juce_graphics/juce_graphics.h index f0d4846de..e133b0ae4 100644 --- a/source/modules/juce_graphics/juce_graphics.h +++ b/source/modules/juce_graphics/juce_graphics.h @@ -35,7 +35,7 @@ ID: juce_graphics vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE graphics classes description: Classes for 2D vector graphics, image loading/saving, font handling, etc. website: http://www.juce.com/juce diff --git a/source/modules/juce_graphics/native/juce_android_Fonts.cpp b/source/modules/juce_graphics/native/juce_android_Fonts.cpp deleted file mode 100644 index 2c82913b2..000000000 --- a/source/modules/juce_graphics/native/juce_android_Fonts.cpp +++ /dev/null @@ -1,549 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -struct DefaultFontNames -{ - DefaultFontNames() - : defaultSans ("sans"), - defaultSerif ("serif"), - defaultFixed ("monospace"), - defaultFallback ("sans") - { - } - - String getRealFontName (const String& faceName) const - { - if (faceName == Font::getDefaultSansSerifFontName()) return defaultSans; - if (faceName == Font::getDefaultSerifFontName()) return defaultSerif; - if (faceName == Font::getDefaultMonospacedFontName()) return defaultFixed; - - return faceName; - } - - String defaultSans, defaultSerif, defaultFixed, defaultFallback; -}; - -Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) -{ - static DefaultFontNames defaultNames; - - Font f (font); - f.setTypefaceName (defaultNames.getRealFontName (font.getTypefaceName())); - return Typeface::createSystemTypefaceFor (f); -} - -//============================================================================== -#if JUCE_USE_FREETYPE - -StringArray FTTypefaceList::getDefaultFontDirectories() -{ - return StringArray ("/system/fonts"); -} - -Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) -{ - return new FreeTypeTypeface (font); -} - -void Typeface::scanFolderForFonts (const File& folder) -{ - FTTypefaceList::getInstance()->scanFontPaths (StringArray (folder.getFullPathName())); -} - -StringArray Font::findAllTypefaceNames() -{ - return FTTypefaceList::getInstance()->findAllFamilyNames(); -} - -StringArray Font::findAllTypefaceStyles (const String& family) -{ - return FTTypefaceList::getInstance()->findAllTypefaceStyles (family); -} - -bool TextLayout::createNativeLayout (const AttributedString&) -{ - return false; -} - -#else - -//============================================================================== -#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") -#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 - -//============================================================================== -StringArray Font::findAllTypefaceNames() -{ - StringArray results; - - for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, "*.ttf")) - results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().upToLastOccurrenceOf ("-", false, false)); - - return results; -} - -StringArray Font::findAllTypefaceStyles (const String& family) -{ - StringArray results ("Regular"); - - for (auto& f : File ("/system/fonts").findChildFiles (File::findFiles, false, family + "-*.ttf")) - results.addIfNotAlreadyThere (f.getFileNameWithoutExtension().fromLastOccurrenceOf ("-", false, false)); - - return results; -} - -const float referenceFontSize = 256.0f; -const float referenceFontToUnits = 1.0f / referenceFontSize; - -//============================================================================== -class AndroidTypeface : public Typeface -{ -public: - AndroidTypeface (const Font& font) - : Typeface (font.getTypefaceName(), font.getTypefaceStyle()), - ascent (0), descent (0), heightToPointsFactor (1.0f) - { - JNIEnv* const env = getEnv(); - - // First check whether there's an embedded asset with this font name: - typeface = GlobalRef (getTypefaceFromAsset (name)); - - if (typeface.get() == nullptr) - { - const bool isBold = style.contains ("Bold"); - const bool isItalic = style.contains ("Italic"); - - File fontFile (getFontFile (name, style)); - - if (! fontFile.exists()) - fontFile = findFontFile (name, isBold, isItalic); - - if (fontFile.exists()) - typeface = GlobalRef (LocalRef(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, - javaString (fontFile.getFullPathName()).get()))); - else - typeface = GlobalRef (LocalRef(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.create, - javaString (getName()).get(), - (isBold ? 1 : 0) + (isItalic ? 2 : 0)))); - } - - initialise (env); - } - - AndroidTypeface (const void* data, size_t size) - : Typeface (String (static_cast (reinterpret_cast (data))), String()) - { - auto* env = getEnv(); - auto cacheFile = getCacheFileForData (data, size); - - typeface = GlobalRef (LocalRef(env->CallStaticObjectMethod (TypefaceClass, TypefaceClass.createFromFile, - javaString (cacheFile.getFullPathName()).get()))); - - initialise (env); - } - - void initialise (JNIEnv* const env) - { - 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)); - const float fullDescent = paint.callFloatMethod (AndroidPaint.descent); - const float totalHeight = fullAscent + fullDescent; - - ascent = fullAscent / totalHeight; - descent = fullDescent / totalHeight; - heightToPointsFactor = referenceFontSize / totalHeight; - } - - float getAscent() const override { return ascent; } - float getDescent() const override { return descent; } - float getHeightToPointsFactor() const override { return heightToPointsFactor; } - - float getStringWidth (const String& text) override - { - JNIEnv* env = getEnv(); - auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()); - jfloatArray widths = env->NewFloatArray ((int) numChars); - - const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths); - - HeapBlock localWidths (static_cast (numDone)); - env->GetFloatArrayRegion (widths, 0, numDone, localWidths); - env->DeleteLocalRef (widths); - - float x = 0; - - for (int i = 0; i < numDone; ++i) - x += localWidths[i]; - - return x * referenceFontToUnits; - } - - void getGlyphPositions (const String& text, Array& glyphs, Array& xOffsets) override - { - JNIEnv* env = getEnv(); - auto numChars = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer()); - jfloatArray widths = env->NewFloatArray ((int) numChars); - - const int numDone = paint.callIntMethod (AndroidPaint.getTextWidths, javaString (text).get(), widths); - - HeapBlock localWidths (static_cast (numDone)); - env->GetFloatArrayRegion (widths, 0, numDone, localWidths); - env->DeleteLocalRef (widths); - - auto s = text.getCharPointer(); - - xOffsets.add (0); - float x = 0; - - for (int i = 0; i < numDone; ++i) - { - const float local = localWidths[i]; - - // Android uses jchar (UTF-16) characters - jchar ch = (jchar) s.getAndAdvance(); - - // Android has no proper glyph support, so we have to do - // a hacky workaround for ligature detection - - #if JUCE_STRING_UTF_TYPE <= 16 - static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two java chars in one glyph"); - - // if the width of this glyph is zero inside the string but has - // a width on it's own, then it's probably due to ligature - if (local == 0.0f && glyphs.size() > 0 && getStringWidth (String (ch)) > 0.0f) - { - // modify the previous glyph - int& glyphNumber = glyphs.getReference (glyphs.size() - 1); - - // make sure this is not a three character ligature - if (glyphNumber < std::numeric_limits::max()) - { - const unsigned int previousGlyph - = static_cast (glyphNumber) & ((1U << (sizeof (jchar) * 8U)) - 1U); - const unsigned int thisGlyph - = static_cast (ch) & ((1U << (sizeof (jchar) * 8U)) - 1U); - - glyphNumber = static_cast ((thisGlyph << (sizeof (jchar) * 8U)) | previousGlyph); - ch = 0; - } - } - #endif - - glyphs.add ((int) ch); - x += local; - xOffsets.add (x * referenceFontToUnits); - } - } - - bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override - { - return false; - } - - EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override - { - #if JUCE_STRING_UTF_TYPE <= 16 - static_assert (sizeof (int) >= (sizeof (jchar) * 2), "Unable store two jni chars in one int"); - - // glyphNumber of zero is used to indicate that the last character was a ligature - if (glyphNumber == 0) return nullptr; - - jchar ch1 = (static_cast (glyphNumber) >> 0) & ((1U << (sizeof (jchar) * 8U)) - 1U); - jchar ch2 = (static_cast (glyphNumber) >> (sizeof (jchar) * 8U)) & ((1U << (sizeof (jchar) * 8U)) - 1U); - #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)); - - - env->CallVoidMethod (paint.get(), AndroidPaint.getCharsPath, charArray.get(), 0, (ch2 != 0 ? 2 : 1), 0.0f, 0.0f, path.get()); - - env->CallVoidMethod (path.get(), AndroidPath.computeBounds, boundsF.get(), 1); - - env->CallBooleanMethod (matrix.get(), AndroidMatrix.mapRect, boundsF.get()); - - 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, (float) -bounds.getX(), (float) -bounds.getY()); - 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; - - if (! bounds.isEmpty()) - { - et = new EdgeTable (bounds); - - jint* const maskDataElements = env->GetIntArrayElements ((jintArray) cachedRenderArray.get(), nullptr); - const jint* mask = maskDataElements; - - for (int y = bounds.getY(); y < bounds.getBottom(); ++y) - { - #if JUCE_LITTLE_ENDIAN - const uint8* const lineBytes = ((const uint8*) mask) + 3; - #else - const uint8* const lineBytes = (const uint8*) mask; - #endif - - et->clipLineToMask (bounds.getX(), y, lineBytes, 4, bounds.getWidth()); - mask += bounds.getWidth(); - } - - env->ReleaseIntArrayElements ((jintArray) cachedRenderArray.get(), maskDataElements, 0); - } - - return et; - } - - GlobalRef typeface, paint, rect, charArray, cachedRenderArray; - float ascent, descent, heightToPointsFactor; - int lastCachedRenderArraySize = -1; - -private: - static File findFontFile (const String& family, - const bool bold, const bool italic) - { - File file; - - if (bold || italic) - { - String suffix; - if (bold) suffix = "Bold"; - if (italic) suffix << "Italic"; - - file = getFontFile (family, suffix); - - if (file.exists()) - return file; - } - - file = getFontFile (family, "Regular"); - - if (! file.exists()) - file = getFontFile (family, String()); - - return file; - } - - static File getFontFile (const String& family, const String& fontStyle) - { - String path ("/system/fonts/" + family); - - if (fontStyle.isNotEmpty()) - path << '-' << fontStyle; - - 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 ((int) 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) -}; - -//============================================================================== -Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) -{ - return new AndroidTypeface (font); -} - -Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t size) -{ - return new AndroidTypeface (data, size); -} - -void Typeface::scanFolderForFonts (const File&) -{ - jassertfalse; // not available unless using FreeType -} - -bool TextLayout::createNativeLayout (const AttributedString&) -{ - return false; -} - -#endif - -} // namespace juce diff --git a/source/modules/juce_graphics/native/juce_android_GraphicsContext.cpp b/source/modules/juce_graphics/native/juce_android_GraphicsContext.cpp deleted file mode 100644 index 4a745e2bf..000000000 --- a/source/modules/juce_graphics/native/juce_android_GraphicsContext.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -namespace GraphicsHelpers -{ - static LocalRef createPaint (Graphics::ResamplingQuality quality) - { - jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/ - | 4 /*DITHER_FLAG*/ - | 128 /*SUBPIXEL_TEXT_FLAG*/; - - if (quality > Graphics::lowResamplingQuality) - constructorFlags |= 2; /*FILTER_BITMAP_FLAG*/ - - return LocalRef(getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags)); - } - - static LocalRef createMatrix (JNIEnv* env, const AffineTransform& t) - { - auto m = LocalRef(env->NewObject (AndroidMatrix, AndroidMatrix.constructor)); - - jfloat values[9] = { t.mat00, t.mat01, t.mat02, - t.mat10, t.mat11, t.mat12, - 0.0f, 0.0f, 1.0f }; - - jfloatArray javaArray = env->NewFloatArray (9); - env->SetFloatArrayRegion (javaArray, 0, 9, values); - - env->CallVoidMethod (m, AndroidMatrix.setValues, javaArray); - env->DeleteLocalRef (javaArray); - - return m; - } -} // namespace GraphicsHelpers - -ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const -{ - return SoftwareImageType().create (format, width, height, clearImage); -} - -} // namespace juce diff --git a/source/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h b/source/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h index 2099bb062..b53ce3216 100644 --- a/source/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h +++ b/source/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTextInterface.h @@ -66,6 +66,9 @@ public: /** Returns a section of text. */ virtual String getText (Range range) const = 0; + /** Returns the full text. */ + String getAllText() const { return getText ({ 0, getTotalNumCharacters() }); } + /** Replaces the text with a new string. */ virtual void setText (const String& newText) = 0; diff --git a/source/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp b/source/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp index 7588bdd14..2ec625207 100644 --- a/source/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp +++ b/source/modules/juce_gui_basics/accessibility/juce_AccessibilityHandler.cpp @@ -63,7 +63,6 @@ AccessibilityHandler::AccessibilityHandler (Component& comp, interfaces (std::move (interfacesIn)), nativeImpl (createNativeImpl (*this)) { - notifyAccessibilityEventInternal (*this, InternalAccessibilityEvent::elementCreated); } AccessibilityHandler::~AccessibilityHandler() diff --git a/source/modules/juce_gui_basics/components/juce_Component.cpp b/source/modules/juce_gui_basics/components/juce_Component.cpp index e2a42bab0..cc7577c5d 100644 --- a/source/modules/juce_gui_basics/components/juce_Component.cpp +++ b/source/modules/juce_gui_basics/components/juce_Component.cpp @@ -327,7 +327,7 @@ struct Component::ComponentHelpers static bool hitTest (Component& comp, Point localPoint) { const auto intPoint = localPoint.roundToInt(); - return Rectangle { comp.getWidth(), comp.getHeight() }.toFloat().contains (localPoint) + return Rectangle { comp.getWidth(), comp.getHeight() }.contains (intPoint) && comp.hitTest (intPoint.x, intPoint.y); } @@ -2943,6 +2943,11 @@ void Component::takeKeyboardFocus (FocusChangeType cause) return; WeakReference componentLosingFocus (currentlyFocusedComponent); + + if (auto* losingFocus = componentLosingFocus.get()) + if (auto* otherPeer = losingFocus->getPeer()) + otherPeer->closeInputMethodContext(); + currentlyFocusedComponent = this; Desktop::getInstance().triggerFocusCallback(); @@ -3008,6 +3013,9 @@ void Component::giveAwayKeyboardFocusInternal (bool sendFocusLossEvent) { if (auto* componentLosingFocus = currentlyFocusedComponent) { + if (auto* otherPeer = componentLosingFocus->getPeer()) + otherPeer->closeInputMethodContext(); + currentlyFocusedComponent = nullptr; if (sendFocusLossEvent && componentLosingFocus != nullptr) @@ -3300,6 +3308,18 @@ AccessibilityHandler* Component::getAccessibilityHandler() || accessibilityHandler->getTypeIndex() != std::type_index (typeid (*this))) { accessibilityHandler = createAccessibilityHandler(); + + // On Android, notifying that an element was created can cause the system to request + // the accessibility node info for the new element. If we're not careful, this will lead + // to recursive calls, as each time an element is created, new node info will be requested, + // causing an element to be created, causing a new info request... + // By assigning the accessibility handler before notifying the system that an element was + // created, the if() predicate above should evaluate to false on recursive calls, + // terminating the recursion. + if (accessibilityHandler != nullptr) + notifyAccessibilityEventInternal (*accessibilityHandler, InternalAccessibilityEvent::elementCreated); + else + jassertfalse; // createAccessibilityHandler must return non-null } return accessibilityHandler.get(); diff --git a/source/modules/juce_gui_basics/juce_gui_basics.cpp b/source/modules/juce_gui_basics/juce_gui_basics.cpp index b4d4190e5..944d25b1c 100644 --- a/source/modules/juce_gui_basics/juce_gui_basics.cpp +++ b/source/modules/juce_gui_basics/juce_gui_basics.cpp @@ -45,6 +45,8 @@ #include "juce_gui_basics.h" +#include + //============================================================================== #if JUCE_MAC #import @@ -66,9 +68,9 @@ #include #include #include + #include #if JUCE_MSVC - #include #include #endif @@ -143,105 +145,104 @@ namespace juce #include "mouse/juce_ComponentDragger.cpp" #include "mouse/juce_DragAndDropContainer.cpp" #include "mouse/juce_MouseEvent.cpp" -// #include "mouse/juce_MouseInactivityDetector.cpp" +#include "mouse/juce_MouseInactivityDetector.cpp" #include "mouse/juce_MouseListener.cpp" #include "keyboard/juce_CaretComponent.cpp" #include "keyboard/juce_KeyboardFocusTraverser.cpp" #include "keyboard/juce_KeyListener.cpp" #include "keyboard/juce_KeyPress.cpp" #include "keyboard/juce_ModifierKeys.cpp" -// #include "buttons/juce_ArrowButton.cpp" +#include "buttons/juce_ArrowButton.cpp" #include "buttons/juce_Button.cpp" #include "buttons/juce_DrawableButton.cpp" -// #include "buttons/juce_HyperlinkButton.cpp" -// #include "buttons/juce_ImageButton.cpp" -// #include "buttons/juce_ShapeButton.cpp" +#include "buttons/juce_HyperlinkButton.cpp" +#include "buttons/juce_ImageButton.cpp" +#include "buttons/juce_ShapeButton.cpp" #include "buttons/juce_TextButton.cpp" -// #include "buttons/juce_ToggleButton.cpp" -// #include "buttons/juce_ToolbarButton.cpp" +#include "buttons/juce_ToggleButton.cpp" +#include "buttons/juce_ToolbarButton.cpp" #include "drawables/juce_Drawable.cpp" #include "drawables/juce_DrawableComposite.cpp" #include "drawables/juce_DrawableImage.cpp" #include "drawables/juce_DrawablePath.cpp" -// #include "drawables/juce_DrawableRectangle.cpp" +#include "drawables/juce_DrawableRectangle.cpp" #include "drawables/juce_DrawableShape.cpp" #include "drawables/juce_DrawableText.cpp" #include "drawables/juce_SVGParser.cpp" #include "filebrowser/juce_DirectoryContentsDisplayComponent.cpp" -// #include "filebrowser/juce_DirectoryContentsList.cpp" -// #include "filebrowser/juce_FileBrowserComponent.cpp" -// #include "filebrowser/juce_FileChooser.cpp" -// #include "filebrowser/juce_FileChooserDialogBox.cpp" -// #include "filebrowser/juce_FileListComponent.cpp" -// #include "filebrowser/juce_FilenameComponent.cpp" -// #include "filebrowser/juce_FileSearchPathListComponent.cpp" -// #include "filebrowser/juce_FileTreeComponent.cpp" -// #include "filebrowser/juce_ImagePreviewComponent.cpp" -// #include "filebrowser/juce_ContentSharer.cpp" +#include "filebrowser/juce_DirectoryContentsList.cpp" +#include "filebrowser/juce_FileBrowserComponent.cpp" +#include "filebrowser/juce_FileChooser.cpp" +#include "filebrowser/juce_FileChooserDialogBox.cpp" +#include "filebrowser/juce_FileListComponent.cpp" +#include "filebrowser/juce_FilenameComponent.cpp" +#include "filebrowser/juce_FileSearchPathListComponent.cpp" +#include "filebrowser/juce_FileTreeComponent.cpp" +#include "filebrowser/juce_ImagePreviewComponent.cpp" +#include "filebrowser/juce_ContentSharer.cpp" #include "layout/juce_ComponentAnimator.cpp" #include "layout/juce_ComponentBoundsConstrainer.cpp" -// #include "layout/juce_ComponentBuilder.cpp" +#include "layout/juce_ComponentBuilder.cpp" #include "layout/juce_ComponentMovementWatcher.cpp" #include "layout/juce_ConcertinaPanel.cpp" -// #include "layout/juce_GroupComponent.cpp" -// #include "layout/juce_MultiDocumentPanel.cpp" +#include "layout/juce_GroupComponent.cpp" +#include "layout/juce_MultiDocumentPanel.cpp" #include "layout/juce_ResizableBorderComponent.cpp" #include "layout/juce_ResizableCornerComponent.cpp" -// #include "layout/juce_ResizableEdgeComponent.cpp" +#include "layout/juce_ResizableEdgeComponent.cpp" #include "layout/juce_ScrollBar.cpp" -// #include "layout/juce_SidePanel.cpp" -// #include "layout/juce_StretchableLayoutManager.cpp" -// #include "layout/juce_StretchableLayoutResizerBar.cpp" +#include "layout/juce_SidePanel.cpp" +#include "layout/juce_StretchableLayoutManager.cpp" +#include "layout/juce_StretchableLayoutResizerBar.cpp" #include "layout/juce_StretchableObjectResizer.cpp" #include "layout/juce_TabbedButtonBar.cpp" #include "layout/juce_TabbedComponent.cpp" #include "layout/juce_Viewport.cpp" #include "lookandfeel/juce_LookAndFeel.cpp" #include "lookandfeel/juce_LookAndFeel_V2.cpp" -// #include "lookandfeel/juce_LookAndFeel_V1.cpp" +#include "lookandfeel/juce_LookAndFeel_V1.cpp" #include "lookandfeel/juce_LookAndFeel_V3.cpp" #include "lookandfeel/juce_LookAndFeel_V4.cpp" #include "menus/juce_MenuBarComponent.cpp" -// #include "menus/juce_BurgerMenuComponent.cpp" +#include "menus/juce_BurgerMenuComponent.cpp" #include "menus/juce_MenuBarModel.cpp" #include "menus/juce_PopupMenu.cpp" -// #include "positioning/juce_MarkerList.cpp" -// #include "positioning/juce_RelativeCoordinate.cpp" -// #include "positioning/juce_RelativeCoordinatePositioner.cpp" -// #include "positioning/juce_RelativeParallelogram.cpp" -// #include "positioning/juce_RelativePoint.cpp" -// #include "positioning/juce_RelativePointPath.cpp" -// #include "positioning/juce_RelativeRectangle.cpp" -// #include "properties/juce_BooleanPropertyComponent.cpp" -// #include "properties/juce_ButtonPropertyComponent.cpp" +#include "positioning/juce_MarkerList.cpp" +#include "positioning/juce_RelativeCoordinate.cpp" +#include "positioning/juce_RelativeCoordinatePositioner.cpp" +#include "positioning/juce_RelativeParallelogram.cpp" +#include "positioning/juce_RelativePoint.cpp" +#include "positioning/juce_RelativePointPath.cpp" +#include "positioning/juce_RelativeRectangle.cpp" +#include "properties/juce_BooleanPropertyComponent.cpp" +#include "properties/juce_ButtonPropertyComponent.cpp" #include "properties/juce_ChoicePropertyComponent.cpp" #include "properties/juce_PropertyComponent.cpp" -// #include "properties/juce_PropertyPanel.cpp" -// #include "properties/juce_SliderPropertyComponent.cpp" -// #include "properties/juce_TextPropertyComponent.cpp" -// #include "properties/juce_MultiChoicePropertyComponent.cpp" +#include "properties/juce_PropertyPanel.cpp" +#include "properties/juce_SliderPropertyComponent.cpp" +#include "properties/juce_TextPropertyComponent.cpp" +#include "properties/juce_MultiChoicePropertyComponent.cpp" #include "widgets/juce_ComboBox.cpp" -// #include "widgets/juce_ImageComponent.cpp" +#include "widgets/juce_ImageComponent.cpp" #include "widgets/juce_Label.cpp" -// #include "widgets/juce_ListBox.cpp" +#include "widgets/juce_ListBox.cpp" #include "widgets/juce_ProgressBar.cpp" #include "widgets/juce_Slider.cpp" #include "widgets/juce_TableHeaderComponent.cpp" -// #include "widgets/juce_TableListBox.cpp" +#include "widgets/juce_TableListBox.cpp" #include "widgets/juce_TextEditor.cpp" -// #include "widgets/juce_ToolbarItemComponent.cpp" -// #include "widgets/juce_Toolbar.cpp" -// #include "widgets/juce_ToolbarItemPalette.cpp" -enum class Async { yes, no }; -// #include "widgets/juce_TreeView.cpp" +#include "widgets/juce_ToolbarItemComponent.cpp" +#include "widgets/juce_Toolbar.cpp" +#include "widgets/juce_ToolbarItemPalette.cpp" +#include "widgets/juce_TreeView.cpp" #include "windows/juce_AlertWindow.cpp" -// #include "windows/juce_CallOutBox.cpp" +#include "windows/juce_CallOutBox.cpp" #include "windows/juce_ComponentPeer.cpp" #include "windows/juce_DialogWindow.cpp" #include "windows/juce_DocumentWindow.cpp" #include "windows/juce_ResizableWindow.cpp" -// #include "windows/juce_ThreadWithProgressWindow.cpp" -// #include "windows/juce_TooltipWindow.cpp" +#include "windows/juce_ThreadWithProgressWindow.cpp" +#include "windows/juce_TooltipWindow.cpp" #include "windows/juce_TopLevelWindow.cpp" #include "commands/juce_ApplicationCommandInfo.cpp" #include "commands/juce_ApplicationCommandManager.cpp" @@ -252,15 +253,15 @@ enum class Async { yes, no }; #include "misc/juce_DropShadower.cpp" #include "misc/juce_FocusOutline.cpp" -// #include "layout/juce_FlexBox.cpp" -// #include "layout/juce_GridItem.cpp" -// #include "layout/juce_Grid.cpp" +#include "layout/juce_FlexBox.cpp" +#include "layout/juce_GridItem.cpp" +#include "layout/juce_Grid.cpp" #if JUCE_IOS || JUCE_WINDOWS #include "native/juce_MultiTouchMapper.h" #endif -#if JUCE_ANDROID || JUCE_WINDOWS +#if JUCE_ANDROID || JUCE_WINDOWS || JUCE_UNIT_TESTS #include "native/accessibility/juce_AccessibilityTextHelpers.h" #endif @@ -271,7 +272,7 @@ enum class Async { yes, no }; #include "native/accessibility/juce_ios_Accessibility.mm" #include "native/juce_ios_UIViewComponentPeer.mm" #include "native/juce_ios_Windowing.mm" -// #include "native/juce_ios_FileChooser.mm" + #include "native/juce_ios_FileChooser.mm" #if JUCE_CONTENT_SHARING #include "native/juce_ios_ContentSharer.cpp" @@ -282,7 +283,7 @@ enum class Async { yes, no }; #include "native/juce_mac_NSViewComponentPeer.mm" #include "native/juce_mac_Windowing.mm" #include "native/juce_mac_MainMenu.mm" -// #include "native/juce_mac_FileChooser.mm" + #include "native/juce_mac_FileChooser.mm" #endif #include "native/juce_mac_MouseCursor.mm" @@ -299,7 +300,7 @@ enum class Async { yes, no }; #endif #include "native/juce_win32_Windowing.cpp" #include "native/juce_win32_DragAndDrop.cpp" -// #include "native/juce_win32_FileChooser.cpp" + #include "native/juce_win32_FileChooser.cpp" #elif JUCE_LINUX || JUCE_BSD #include "native/x11/juce_linux_X11_Symbols.cpp" @@ -312,13 +313,13 @@ enum class Async { yes, no }; JUCE_END_IGNORE_WARNINGS_GCC_LIKE -// #include "native/juce_linux_FileChooser.cpp" + #include "native/juce_linux_FileChooser.cpp" #elif JUCE_ANDROID + #include "juce_core/files/juce_common_MimeTypes.h" #include "native/accessibility/juce_android_Accessibility.cpp" #include "native/juce_android_Windowing.cpp" - #include "native/juce_common_MimeTypes.cpp" -// #include "native/juce_android_FileChooser.cpp" + #include "native/juce_android_FileChooser.cpp" #if JUCE_CONTENT_SHARING #include "native/juce_android_ContentSharer.cpp" @@ -412,3 +413,7 @@ bool juce::isWindowOnCurrentVirtualDesktop (void* x) // Depends on types defined in platform-specific windowing files #include "mouse/juce_MouseCursor.cpp" + +#if JUCE_UNIT_TESTS +#include "native/accessibility/juce_AccessibilityTextHelpers_test.cpp" +#endif diff --git a/source/modules/juce_gui_basics/juce_gui_basics.h b/source/modules/juce_gui_basics/juce_gui_basics.h index 28097d128..3f7368dd1 100644 --- a/source/modules/juce_gui_basics/juce_gui_basics.h +++ b/source/modules/juce_gui_basics/juce_gui_basics.h @@ -35,7 +35,7 @@ ID: juce_gui_basics vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE GUI core classes description: Basic user-interface components and related classes. website: http://www.juce.com/juce diff --git a/source/modules/juce_gui_basics/layout/juce_Grid.cpp b/source/modules/juce_gui_basics/layout/juce_Grid.cpp index 36a397f50..aa143a350 100644 --- a/source/modules/juce_gui_basics/layout/juce_Grid.cpp +++ b/source/modules/juce_gui_basics/layout/juce_Grid.cpp @@ -597,6 +597,11 @@ struct Grid::AutoPlacement return referenceCell; } + void updateMaxCrossDimensionFromAutoPlacementItem (int columnSpan, int rowSpan) + { + highestCrossDimension = jmax (highestCrossDimension, 1 + getCrossDimension ({ columnSpan, rowSpan })); + } + private: struct SortableCell { @@ -642,9 +647,10 @@ struct Grid::AutoPlacement bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const { - const auto crossSpan = columnFirst ? rowSpan : columnSpan; + const auto highestIndexOfCell = getCrossDimension (cell) + getCrossDimension ({ columnSpan, rowSpan }); + const auto highestIndexOfGrid = getHighestCrossDimension(); - return (getCrossDimension (cell) + crossSpan) > getHighestCrossDimension(); + return highestIndexOfGrid < highestIndexOfCell; } int getHighestCrossDimension() const @@ -807,6 +813,11 @@ struct Grid::AutoPlacement } } + // https://www.w3.org/TR/css-grid-1/#auto-placement-algo step 3.3 + for (auto* item : sortedItems) + if (hasAutoPlacement (*item)) + plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->column), getSpanFromAuto (item->row)); + lastInsertionCell = { 1, 1 }; for (auto* item : sortedItems) diff --git a/source/modules/juce_gui_basics/layout/juce_Viewport.cpp b/source/modules/juce_gui_basics/layout/juce_Viewport.cpp index c30276326..e227a2ea4 100644 --- a/source/modules/juce_gui_basics/layout/juce_Viewport.cpp +++ b/source/modules/juce_gui_basics/layout/juce_Viewport.cpp @@ -61,6 +61,12 @@ struct Viewport::DragToScrollListener : private MouseListener, Desktop::getInstance().removeGlobalMouseListener (this); } + void stopOngoingAnimation() + { + offsetX.setPosition (offsetX.getPosition()); + offsetY.setPosition (offsetY.getPosition()); + } + void positionChanged (ViewportDragPosition&, double) override { viewport.setViewPosition (originalViewPos - Point ((int) offsetX.getPosition(), @@ -119,9 +125,11 @@ struct Viewport::DragToScrollListener : private MouseListener, void endDragAndClearGlobalMouseListener() { - offsetX.endDrag(); - offsetY.endDrag(); - isDragging = false; + if (std::exchange (isDragging, false) == true) + { + offsetX.endDrag(); + offsetY.endDrag(); + } viewport.contentHolder.addMouseListener (this, true); Desktop::getInstance().removeGlobalMouseListener (this); @@ -229,6 +237,8 @@ void Viewport::recreateScrollbars() getVerticalScrollBar().addListener (this); getHorizontalScrollBar().addListener (this); + getVerticalScrollBar().addMouseListener (this, true); + getHorizontalScrollBar().addMouseListener (this, true); resized(); } @@ -531,8 +541,15 @@ void Viewport::scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRange void Viewport::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel) { - if (! useMouseWheelMoveIfNeeded (e, wheel)) - Component::mouseWheelMove (e, wheel); + if (e.eventComponent == this) + if (! useMouseWheelMoveIfNeeded (e, wheel)) + Component::mouseWheelMove (e, wheel); +} + +void Viewport::mouseDown (const MouseEvent& e) +{ + if (e.eventComponent == horizontalScrollBar.get() || e.eventComponent == verticalScrollBar.get()) + dragToScrollListener->stopOngoingAnimation(); } static int rescaleMouseWheelDistance (float distance, int singleStepSize) noexcept diff --git a/source/modules/juce_gui_basics/layout/juce_Viewport.h b/source/modules/juce_gui_basics/layout/juce_Viewport.h index 071f4be64..17762b1cd 100644 --- a/source/modules/juce_gui_basics/layout/juce_Viewport.h +++ b/source/modules/juce_gui_basics/layout/juce_Viewport.h @@ -318,6 +318,8 @@ public: /** @internal */ void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override; /** @internal */ + void mouseDown (const MouseEvent& e) override; + /** @internal */ bool keyPressed (const KeyPress&) override; /** @internal */ void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override; @@ -337,8 +339,16 @@ protected: private: //============================================================================== + class AccessibilityIgnoredComponent : public Component + { + std::unique_ptr createAccessibilityHandler() override + { + return createIgnoredAccessibilityHandler (*this); + } + }; + std::unique_ptr verticalScrollBar, horizontalScrollBar; - Component contentHolder; + AccessibilityIgnoredComponent contentHolder; WeakReference contentComp; Rectangle lastVisibleArea; int scrollBarThickness = 0; diff --git a/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp b/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp index f4dbd2898..ef413750d 100644 --- a/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp +++ b/source/modules/juce_gui_basics/menus/juce_PopupMenu.cpp @@ -87,7 +87,7 @@ struct HeaderItemComponent : public PopupMenu::CustomComponent std::unique_ptr createAccessibilityHandler() override { - return nullptr; + return createIgnoredAccessibilityHandler (*this); } const Options& options; @@ -275,7 +275,8 @@ private: std::unique_ptr createAccessibilityHandler() override { - return item.isSeparator ? nullptr : std::make_unique (*this); + return item.isSeparator ? createIgnoredAccessibilityHandler (*this) + : std::make_unique (*this); } //============================================================================== diff --git a/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp b/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp index 73b182bc2..e534e1909 100644 --- a/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp +++ b/source/modules/juce_gui_basics/mouse/juce_DragAndDropContainer.cpp @@ -59,6 +59,7 @@ public: startTimer (200); setInterceptsMouseClicks (false, false); + setWantsKeyboardFocus (true); setAlwaysOnTop (true); } @@ -200,7 +201,12 @@ public: { if (key == KeyPress::escapeKey) { - dismissWithAnimation (true); + const auto wasVisible = isVisible(); + setVisible (false); + + if (wasVisible) + dismissWithAnimation (true); + deleteSelf(); return true; } @@ -466,8 +472,7 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, dragImageComponent->setOpaque (true); dragImageComponent->addToDesktop (ComponentPeer::windowIgnoresMouseClicks - | ComponentPeer::windowIsTemporary - | ComponentPeer::windowIgnoresKeyPresses); + | ComponentPeer::windowIsTemporary); } else { @@ -484,6 +489,7 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, dragImageComponent->sourceDetails.localPosition = sourceComponent->getLocalPoint (nullptr, lastMouseDown); dragImageComponent->updateLocation (false, lastMouseDown); + dragImageComponent->grabKeyboardFocus(); #if JUCE_WINDOWS // Under heavy load, the layered window's paint callback can often be lost by the OS, diff --git a/source/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h b/source/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h index 97bc65be6..35ca63fed 100644 --- a/source/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h +++ b/source/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h @@ -26,8 +26,58 @@ namespace juce { -namespace AccessibilityTextHelpers +struct AccessibilityTextHelpers { + /* Wraps a CharPtr into a stdlib-compatible iterator. + + MSVC's std::reverse_iterator requires the wrapped iterator to be default constructible + when building in C++20 mode, but I don't really want to add public default constructors to + the CharPtr types. Instead, we add a very basic default constructor here which sets the + wrapped CharPtr to nullptr. + */ + template + class CharPtrIteratorAdapter + { + public: + using difference_type = int; + using value_type = decltype (*std::declval()); + using pointer = value_type*; + using reference = value_type; + using iterator_category = std::bidirectional_iterator_tag; + + CharPtrIteratorAdapter() = default; + constexpr explicit CharPtrIteratorAdapter (CharPtr arg) : ptr (arg) {} + + constexpr auto operator*() const { return *ptr; } + + constexpr CharPtrIteratorAdapter& operator++() + { + ++ptr; + return *this; + } + + constexpr CharPtrIteratorAdapter& operator--() + { + --ptr; + return *this; + } + + constexpr bool operator== (const CharPtrIteratorAdapter& other) const { return ptr == other.ptr; } + constexpr bool operator!= (const CharPtrIteratorAdapter& other) const { return ptr != other.ptr; } + + constexpr auto operator+ (difference_type offset) const { return CharPtrIteratorAdapter { ptr + offset }; } + constexpr auto operator- (difference_type offset) const { return CharPtrIteratorAdapter { ptr - offset }; } + + private: + CharPtr ptr { {} }; + }; + + template + static auto makeCharPtrIteratorAdapter (CharPtr ptr) + { + return CharPtrIteratorAdapter { ptr }; + } + enum class BoundaryType { character, @@ -42,60 +92,177 @@ namespace AccessibilityTextHelpers backwards }; - static int findTextBoundary (const AccessibilityTextInterface& textInterface, - int currentPosition, - BoundaryType boundary, - Direction direction) + enum class ExtendSelection { - const auto numCharacters = textInterface.getTotalNumCharacters(); - const auto isForwards = (direction == Direction::forwards); - const auto offsetWithDirection = [isForwards] (auto num) { return isForwards ? num : -num; }; + no, + yes + }; - switch (boundary) + /* Indicates whether a function may return the current text position, in the case that the + position already falls on a text unit boundary. + */ + enum class IncludeThisBoundary + { + no, //< Always search for the following boundary, even if the current position falls on a boundary + yes //< Return the current position if it falls on a boundary + }; + + /* Indicates whether a word boundary should include any whitespaces that follow the + non-whitespace characters. + */ + enum class IncludeWhitespaceAfterWords + { + no, //< The word ends on the first whitespace character + yes //< The word ends after the last whitespace character + }; + + /* Like std::distance, but always does an O(N) count rather than an O(1) count, and doesn't + require the iterators to have any member type aliases. + */ + template + static int countDifference (Iter from, Iter to) + { + int distance = 0; + + while (from != to) { - case BoundaryType::character: - return jlimit (0, numCharacters, currentPosition + offsetWithDirection (1)); + ++from; + ++distance; + } - case BoundaryType::word: - case BoundaryType::line: + return distance; + } + + /* Returns the number of characters between ptr and the next word end in a specific + direction. + + If ptr is inside a word, the result will be the distance to the end of the same + word. + */ + template + static int findNextWordEndOffset (CharPtr beginIn, + CharPtr endIn, + CharPtr ptrIn, + Direction direction, + IncludeThisBoundary includeBoundary, + IncludeWhitespaceAfterWords includeWhitespace) + { + const auto begin = makeCharPtrIteratorAdapter (beginIn); + const auto end = makeCharPtrIteratorAdapter (endIn); + const auto ptr = makeCharPtrIteratorAdapter (ptrIn); + + const auto move = [&] (auto b, auto e, auto iter) + { + const auto isSpace = [] (juce_wchar c) { return CharacterFunctions::isWhitespace (c); }; + + const auto start = [&] { - const auto text = [&]() -> String - { - if (isForwards) - return textInterface.getText ({ currentPosition, textInterface.getTotalNumCharacters() }); + if (iter == b && includeBoundary == IncludeThisBoundary::yes) + return b; - const auto str = textInterface.getText ({ 0, currentPosition }); + const auto nudged = iter - (iter != b && includeBoundary == IncludeThisBoundary::yes ? 1 : 0); - auto start = str.getCharPointer(); - auto end = start.findTerminatingNull(); - const auto size = getAddressDifference (end.getAddress(), start.getAddress()); + return includeWhitespace == IncludeWhitespaceAfterWords::yes + ? std::find_if (nudged, e, isSpace) + : std::find_if_not (nudged, e, isSpace); + }(); - String reversed; + const auto found = includeWhitespace == IncludeWhitespaceAfterWords::yes + ? std::find_if_not (start, e, isSpace) + : std::find_if (start, e, isSpace); - if (size > 0) - { - reversed.preallocateBytes ((size_t) size); + return countDifference (iter, found); + }; - auto destPtr = reversed.getCharPointer(); + return direction == Direction::forwards ? move (begin, end, ptr) + : -move (std::make_reverse_iterator (end), + std::make_reverse_iterator (begin), + std::make_reverse_iterator (ptr)); + } - for (;;) - { - destPtr.write (*--end); + /* Returns the number of characters between ptr and the beginning of the next line in a + specific direction. + */ + template + static int findNextLineOffset (CharPtr beginIn, + CharPtr endIn, + CharPtr ptrIn, + Direction direction, + IncludeThisBoundary includeBoundary) + { + const auto begin = makeCharPtrIteratorAdapter (beginIn); + const auto end = makeCharPtrIteratorAdapter (endIn); + const auto ptr = makeCharPtrIteratorAdapter (ptrIn); - if (end == start) - break; - } + const auto findNewline = [] (auto from, auto to) { return std::find (from, to, juce_wchar { '\n' }); }; - destPtr.writeNull(); - } + if (direction == Direction::forwards) + { + if (ptr != begin && includeBoundary == IncludeThisBoundary::yes && *(ptr - 1) == '\n') + return 0; - return reversed; - }(); + const auto newline = findNewline (ptr, end); + return countDifference (ptr, newline) + (newline == end ? 0 : 1); + } - auto tokens = (boundary == BoundaryType::line ? StringArray::fromLines (text) - : StringArray::fromTokens (text, false)); + const auto rbegin = std::make_reverse_iterator (ptr); + const auto rend = std::make_reverse_iterator (begin); - return currentPosition + offsetWithDirection (tokens[0].length()); + return -countDifference (rbegin, findNewline (rbegin + (rbegin == rend || includeBoundary == IncludeThisBoundary::yes ? 0 : 1), rend)); + } + + /* Unfortunately, the method of computing end-points of text units depends on context, and on + the current platform. + + Some examples of different behaviour: + - On Android, updating the cursor/selection always searches for the next text unit boundary; + but on Windows, ExpandToEnclosingUnit() should not move the starting point of the + selection if it already at a unit boundary. This means that we need both inclusive and + exclusive methods for finding the next text boundary. + - On Android, moving the cursor by 'words' should move to the first space following a + non-space character in the requested direction. On Windows, a 'word' includes trailing + whitespace, but not preceding whitespace. This means that we need a way of specifying + whether whitespace should be included when navigating by words. + */ + static int findTextBoundary (const AccessibilityTextInterface& textInterface, + int currentPosition, + BoundaryType boundary, + Direction direction, + IncludeThisBoundary includeBoundary, + IncludeWhitespaceAfterWords includeWhitespace) + { + const auto numCharacters = textInterface.getTotalNumCharacters(); + const auto isForwards = (direction == Direction::forwards); + const auto currentClamped = jlimit (0, numCharacters, currentPosition); + + switch (boundary) + { + case BoundaryType::character: + { + const auto offset = includeBoundary == IncludeThisBoundary::yes ? 0 + : (isForwards ? 1 : -1); + return jlimit (0, numCharacters, currentPosition + offset); + } + + case BoundaryType::word: + { + const auto str = textInterface.getText ({ 0, numCharacters }); + return currentClamped + findNextWordEndOffset (str.begin(), + str.end(), + str.begin() + currentClamped, + direction, + includeBoundary, + includeWhitespace); + } + + case BoundaryType::line: + { + const auto str = textInterface.getText ({ 0, numCharacters }); + return currentClamped + findNextLineOffset (str.begin(), + str.end(), + str.begin() + currentClamped, + direction, + includeBoundary); } case BoundaryType::document: @@ -105,6 +272,31 @@ namespace AccessibilityTextHelpers jassertfalse; return -1; } -} + + /* Adjusts the current text selection range, using an algorithm appropriate for cursor movement + on Android. + */ + static Range findNewSelectionRangeAndroid (const AccessibilityTextInterface& textInterface, + BoundaryType boundaryType, + ExtendSelection extend, + Direction direction) + { + const auto oldPos = textInterface.getTextInsertionOffset(); + const auto cursorPos = findTextBoundary (textInterface, + oldPos, + boundaryType, + direction, + IncludeThisBoundary::no, + IncludeWhitespaceAfterWords::no); + + if (extend == ExtendSelection::no) + return { cursorPos, cursorPos }; + + const auto currentSelection = textInterface.getSelection(); + const auto start = currentSelection.getStart(); + const auto end = currentSelection.getEnd(); + return Range::between (cursorPos, oldPos == start ? end : start); + } +}; } // namespace juce diff --git a/source/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp b/source/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp index 06b5a198c..6eeae8d64 100644 --- a/source/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp +++ b/source/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp @@ -59,7 +59,10 @@ namespace juce #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (obtain, "obtain", "(I)Landroid/view/accessibility/AccessibilityEvent;") \ METHOD (setPackageName, "setPackageName", "(Ljava/lang/CharSequence;)V") \ - METHOD (setSource, "setSource", "(Landroid/view/View;I)V") \ + METHOD (setSource, "setSource","(Landroid/view/View;I)V") \ + METHOD (setAction, "setAction", "(I)V") \ + METHOD (setFromIndex, "setFromIndex", "(I)V") \ + METHOD (setToIndex, "setToIndex", "(I)V") \ DECLARE_JNI_CLASS (AndroidAccessibilityEvent, "android/view/accessibility/AccessibilityEvent") #undef JNI_CLASS_MEMBERS @@ -74,13 +77,14 @@ namespace { constexpr int HOST_VIEW_ID = -1; - constexpr int TYPE_VIEW_CLICKED = 0x00000001, - TYPE_VIEW_SELECTED = 0x00000004, - TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000, - TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000, - TYPE_WINDOW_CONTENT_CHANGED = 0x00000800, - TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000, - TYPE_VIEW_TEXT_CHANGED = 0x00000010; + constexpr int TYPE_VIEW_CLICKED = 0x00000001, + TYPE_VIEW_SELECTED = 0x00000004, + TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000, + TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000, + TYPE_WINDOW_CONTENT_CHANGED = 0x00000800, + TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000, + TYPE_VIEW_TEXT_CHANGED = 0x00000010, + TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000; constexpr int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001, CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004; @@ -198,8 +202,6 @@ static jobject getSourceView (const AccessibilityHandler& handler) return nullptr; } -void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes); - //============================================================================== class AccessibilityNativeHandle { @@ -377,7 +379,7 @@ public: { env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setText, - javaString (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() })).get()); + javaString (textInterface->getAllText()).get()); const auto isReadOnly = textInterface->isReadOnly(); @@ -481,10 +483,15 @@ public: case ACTION_CLICK: { + // Invoking the action may delete this handler + const WeakReference savedHandle { this }; + if ((accessibilityHandler.getCurrentState().isCheckable() && accessibilityHandler.getActions().invoke (AccessibilityActionType::toggle)) || accessibilityHandler.getActions().invoke (AccessibilityActionType::press)) { - sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_CLICKED, 0); + if (savedHandle != nullptr) + sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_CLICKED, 0); + return true; } @@ -556,7 +563,10 @@ public: return env->CallIntMethod (arguments, AndroidBundle.getInt, key.get()); }; - return { getKey (selectionStartKey), getKey (selectionEndKey) }; + const auto start = getKey (selectionStartKey); + const auto end = getKey (selectionEndKey); + + return Range::between (start, end); } return {}; @@ -636,6 +646,78 @@ public: bool isInPopulateNodeInfo() const noexcept { return inPopulateNodeInfo; } + static bool areAnyAccessibilityClientsActive() + { + auto* env = getEnv(); + auto appContext = getAppContext(); + + if (appContext.get() != nullptr) + { + LocalRef accessibilityManager (env->CallObjectMethod (appContext.get(), AndroidContext.getSystemService, + javaString ("accessibility").get())); + + if (accessibilityManager != nullptr) + return env->CallBooleanMethod (accessibilityManager.get(), AndroidAccessibilityManager.isEnabled); + } + + return false; + } + + template + static void sendAccessibilityEventExtendedImpl (const AccessibilityHandler& handler, + int eventType, + ModificationCallback&& modificationCallback) + { + if (! areAnyAccessibilityClientsActive()) + return; + + if (const auto sourceView = getSourceView (handler)) + { + const auto* nativeImpl = handler.getNativeImplementation(); + + if (nativeImpl == nullptr || nativeImpl->isInPopulateNodeInfo()) + return; + + auto* env = getEnv(); + auto appContext = getAppContext(); + + if (appContext.get() == nullptr) + return; + + LocalRef event (env->CallStaticObjectMethod (AndroidAccessibilityEvent, + AndroidAccessibilityEvent.obtain, + eventType)); + + env->CallVoidMethod (event, + AndroidAccessibilityEvent.setPackageName, + env->CallObjectMethod (appContext.get(), + AndroidContext.getPackageName)); + + env->CallVoidMethod (event, + AndroidAccessibilityEvent.setSource, + sourceView, + nativeImpl->getVirtualViewId()); + + modificationCallback (event); + + env->CallBooleanMethod (sourceView, + AndroidViewGroup.requestSendAccessibilityEvent, + sourceView, + event.get()); + } + } + + static void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes) + { + sendAccessibilityEventExtendedImpl (handler, eventType, [contentChangeTypes] (auto event) + { + if (contentChangeTypes != 0 && accessibilityEventSetContentChangeTypes != nullptr) + getEnv()->CallVoidMethod (event, + accessibilityEventSetContentChangeTypes, + contentChangeTypes); + }); + } + private: static std::unordered_map virtualViewIdMap; @@ -654,7 +736,7 @@ private: const auto valueString = [this]() -> String { if (auto* textInterface = accessibilityHandler.getTextInterface()) - return textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }); + return textInterface->getAllText(); if (auto* valueInterface = accessibilityHandler.getValueInterface()) return valueInterface->getCurrentValueAsString(); @@ -674,66 +756,68 @@ private: bool moveCursor (jobject arguments, bool forwards) { - if (auto* textInterface = accessibilityHandler.getTextInterface()) - { - const auto granularityKey = javaString ("ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"); - const auto extendSelectionKey = javaString ("ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN"); + using ATH = AccessibilityTextHelpers; - auto* env = getEnv(); + auto* textInterface = accessibilityHandler.getTextInterface(); - const auto boundaryType = [&] - { - const auto granularity = env->CallIntMethod (arguments, - AndroidBundle.getInt, - granularityKey.get()); + if (textInterface == nullptr) + return false; - using BoundaryType = AccessibilityTextHelpers::BoundaryType; + const auto granularityKey = javaString ("ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"); + const auto extendSelectionKey = javaString ("ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN"); - switch (granularity) - { - case MOVEMENT_GRANULARITY_CHARACTER: return BoundaryType::character; - case MOVEMENT_GRANULARITY_WORD: return BoundaryType::word; - case MOVEMENT_GRANULARITY_LINE: return BoundaryType::line; - case MOVEMENT_GRANULARITY_PARAGRAPH: - case MOVEMENT_GRANULARITY_PAGE: return BoundaryType::document; - } - - jassertfalse; - return BoundaryType::character; - }(); + auto* env = getEnv(); - using Direction = AccessibilityTextHelpers::Direction; + const auto boundaryType = [&] + { + const auto granularity = env->CallIntMethod (arguments, AndroidBundle.getInt, granularityKey.get()); - const auto cursorPos = AccessibilityTextHelpers::findTextBoundary (*textInterface, - textInterface->getTextInsertionOffset(), - boundaryType, - forwards ? Direction::forwards - : Direction::backwards); + using BoundaryType = ATH::BoundaryType; - const auto newSelection = [&]() -> Range + switch (granularity) { - const auto currentSelection = textInterface->getSelection(); - const auto extendSelection = env->CallBooleanMethod (arguments, - AndroidBundle.getBoolean, - extendSelectionKey.get()); + case MOVEMENT_GRANULARITY_CHARACTER: return BoundaryType::character; + case MOVEMENT_GRANULARITY_WORD: return BoundaryType::word; + case MOVEMENT_GRANULARITY_LINE: return BoundaryType::line; + case MOVEMENT_GRANULARITY_PARAGRAPH: + case MOVEMENT_GRANULARITY_PAGE: return BoundaryType::document; + } - if (! extendSelection) - return { cursorPos, cursorPos }; + jassertfalse; + return BoundaryType::character; + }(); - const auto start = currentSelection.getStart(); - const auto end = currentSelection.getEnd(); + const auto direction = forwards + ? ATH::Direction::forwards + : ATH::Direction::backwards; - if (forwards) - return { start, jmax (start, cursorPos) }; + const auto extend = env->CallBooleanMethod (arguments, AndroidBundle.getBoolean, extendSelectionKey.get()) + ? ATH::ExtendSelection::yes + : ATH::ExtendSelection::no; - return { jmin (start, cursorPos), end }; - }(); + const auto oldSelection = textInterface->getSelection(); + const auto newSelection = ATH::findNewSelectionRangeAndroid (*textInterface, boundaryType, extend, direction); + textInterface->setSelection (newSelection); - textInterface->setSelection (newSelection); - return true; - } + // Required for Android to read back the text that the cursor moved over + sendAccessibilityEventExtendedImpl (accessibilityHandler, TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, [&] (auto event) + { + env->CallVoidMethod (event, + AndroidAccessibilityEvent.setAction, + forwards ? ACTION_NEXT_AT_MOVEMENT_GRANULARITY : ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); - return false; + env->CallVoidMethod (event, + AndroidAccessibilityEvent.setFromIndex, + oldSelection.getStart() != newSelection.getStart() ? oldSelection.getStart() + : oldSelection.getEnd()); + + env->CallVoidMethod (event, + AndroidAccessibilityEvent.setToIndex, + oldSelection.getStart() != newSelection.getStart() ? newSelection.getStart() + : newSelection.getEnd()); + }); + + return true; } AccessibilityHandler& accessibilityHandler; @@ -742,6 +826,8 @@ private: //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle) + + JUCE_DECLARE_WEAK_REFERENCEABLE (AccessibilityNativeHandle) }; std::unordered_map AccessibilityNativeHandle::virtualViewIdMap; @@ -758,67 +844,6 @@ AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const return nativeImpl.get(); } -static bool areAnyAccessibilityClientsActive() -{ - auto* env = getEnv(); - auto appContext = getAppContext(); - - if (appContext.get() != nullptr) - { - LocalRef accessibilityManager (env->CallObjectMethod (appContext.get(), AndroidContext.getSystemService, - javaString ("accessibility").get())); - - if (accessibilityManager != nullptr) - return env->CallBooleanMethod (accessibilityManager.get(), AndroidAccessibilityManager.isEnabled); - } - - return false; -} - -void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes) -{ - if (! areAnyAccessibilityClientsActive()) - return; - - if (const auto sourceView = getSourceView (handler)) - { - const auto* nativeImpl = handler.getNativeImplementation(); - - if (nativeImpl == nullptr || nativeImpl->isInPopulateNodeInfo()) - return; - - auto* env = getEnv(); - auto appContext = getAppContext(); - - if (appContext.get() == nullptr) - return; - - LocalRef event (env->CallStaticObjectMethod (AndroidAccessibilityEvent, - AndroidAccessibilityEvent.obtain, - eventType)); - - env->CallVoidMethod (event, - AndroidAccessibilityEvent.setPackageName, - env->CallObjectMethod (appContext.get(), - AndroidContext.getPackageName)); - - env->CallVoidMethod (event, - AndroidAccessibilityEvent.setSource, - sourceView, - nativeImpl->getVirtualViewId()); - - if (contentChangeTypes != 0 && accessibilityEventSetContentChangeTypes != nullptr) - env->CallVoidMethod (event, - accessibilityEventSetContentChangeTypes, - contentChangeTypes); - - env->CallBooleanMethod (sourceView, - AndroidViewGroup.requestSendAccessibilityEvent, - sourceView, - event.get()); - } -} - void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType) { @@ -827,7 +852,7 @@ void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, || eventType == InternalAccessibilityEvent::elementMovedOrResized) { if (auto* parent = handler.getParent()) - sendAccessibilityEventImpl (*parent, TYPE_WINDOW_CONTENT_CHANGED, CONTENT_CHANGE_TYPE_SUBTREE); + AccessibilityNativeHandle::sendAccessibilityEventImpl (*parent, TYPE_WINDOW_CONTENT_CHANGED, CONTENT_CHANGE_TYPE_SUBTREE); return; } @@ -852,7 +877,7 @@ void notifyAccessibilityEventInternal (const AccessibilityHandler& handler, }(); if (notification != 0) - sendAccessibilityEventImpl (handler, notification, 0); + AccessibilityNativeHandle::sendAccessibilityEventImpl (handler, notification, 0); } void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const @@ -885,13 +910,13 @@ void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventTyp return 0; }(); - sendAccessibilityEventImpl (*this, notification, contentChangeTypes); + AccessibilityNativeHandle::sendAccessibilityEventImpl (*this, notification, contentChangeTypes); } void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority) { - if (! areAnyAccessibilityClientsActive()) + if (! AccessibilityNativeHandle::areAnyAccessibilityClientsActive()) return; const auto rootView = [] diff --git a/source/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp b/source/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp index 88d68d95c..9be3ac5e3 100644 --- a/source/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp +++ b/source/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp @@ -206,10 +206,14 @@ void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventTyp { if (auto* valueInterface = getValueInterface()) { - VARIANT newValue; - VariantHelpers::setString (valueInterface->getCurrentValueAsString(), &newValue); + const auto propertyType = getRole() == AccessibilityRole::slider ? UIA_RangeValueValuePropertyId + : UIA_ValueValuePropertyId; - sendAccessibilityPropertyChangedEvent (*this, UIA_ValueValuePropertyId, newValue); + const auto value = getRole() == AccessibilityRole::slider + ? VariantHelpers::getWithValue (valueInterface->getCurrentValue()) + : VariantHelpers::getWithValue (valueInterface->getCurrentValueAsString()); + + sendAccessibilityPropertyChangedEvent (*this, propertyType, value); } return; diff --git a/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h b/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h index a234fceb9..a8769b3cf 100644 --- a/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h +++ b/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h @@ -28,6 +28,17 @@ namespace juce namespace VariantHelpers { + namespace Detail + { + template + inline VARIANT getWithValueGeneric (Fn&& setter, ValueType value) + { + VARIANT result{}; + setter (value, &result); + return result; + } + } + inline void clear (VARIANT* variant) { variant->vt = VT_EMPTY; @@ -56,6 +67,9 @@ namespace VariantHelpers variant->vt = VT_R8; variant->dblVal = value; } + + inline VARIANT getWithValue (double value) { return Detail::getWithValueGeneric (&setDouble, value); } + inline VARIANT getWithValue (const String& value) { return Detail::getWithValueGeneric (&setString, value); } } inline JUCE_COMRESULT addHandlersToArray (const std::vector& handlers, SAFEARRAY** pRetVal) diff --git a/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h b/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h index c4a312cbd..7a6224c82 100644 --- a/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h +++ b/source/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h @@ -246,19 +246,22 @@ private: if (auto* textInterface = owner->getHandler().getTextInterface()) { - const auto boundaryType = getBoundaryType (unit); - - const auto start = unit == ComTypes::TextUnit::TextUnit_Character - ? selectionRange.getStart() - : AccessibilityTextHelpers::findTextBoundary (*textInterface, - selectionRange.getStart(), - boundaryType, - AccessibilityTextHelpers::Direction::backwards); + using ATH = AccessibilityTextHelpers; - const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface, - start, - boundaryType, - AccessibilityTextHelpers::Direction::forwards); + const auto boundaryType = getBoundaryType (unit); + const auto start = ATH::findTextBoundary (*textInterface, + selectionRange.getStart(), + boundaryType, + ATH::Direction::backwards, + ATH::IncludeThisBoundary::yes, + ATH::IncludeWhitespaceAfterWords::no); + + const auto end = ATH::findTextBoundary (*textInterface, + start, + boundaryType, + ATH::Direction::forwards, + ATH::IncludeThisBoundary::no, + ATH::IncludeWhitespaceAfterWords::yes); selectionRange = Range (start, end); @@ -413,19 +416,39 @@ private: JUCE_COMRESULT Move (ComTypes::TextUnit unit, int count, int* pRetVal) override { - return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&) + return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface) { - if (count > 0) - { - MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal); - MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal); - } - else if (count < 0) + using ATH = AccessibilityTextHelpers; + + const auto boundaryType = getBoundaryType (unit); + const auto previousUnitBoundary = ATH::findTextBoundary (textInterface, + selectionRange.getStart(), + boundaryType, + ATH::Direction::backwards, + ATH::IncludeThisBoundary::yes, + ATH::IncludeWhitespaceAfterWords::no); + + auto numMoved = 0; + auto movedEndpoint = previousUnitBoundary; + + for (; numMoved < std::abs (count); ++numMoved) { - MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_Start, unit, count, pRetVal); - MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint_End, unit, count, pRetVal); + const auto nextEndpoint = ATH::findTextBoundary (textInterface, + movedEndpoint, + boundaryType, + count > 0 ? ATH::Direction::forwards : ATH::Direction::backwards, + ATH::IncludeThisBoundary::no, + count > 0 ? ATH::IncludeWhitespaceAfterWords::yes : ATH::IncludeWhitespaceAfterWords::no); + + if (nextEndpoint == movedEndpoint) + break; + + movedEndpoint = nextEndpoint; } + *pRetVal = numMoved; + + ExpandToEnclosingUnit (unit); return S_OK; }); } @@ -463,34 +486,37 @@ private: if (count == 0 || textInterface.getTotalNumCharacters() == 0) return S_OK; - auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart() - : selectionRange.getEnd()); + const auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart() + : selectionRange.getEnd()); - const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards - : AccessibilityTextHelpers::Direction::backwards); + using ATH = AccessibilityTextHelpers; - const auto boundaryType = getBoundaryType (unit); + const auto direction = (count > 0 ? ATH::Direction::forwards + : ATH::Direction::backwards); - // handle case where endpoint is on a boundary - if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove) - endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1); + const auto boundaryType = getBoundaryType (unit); + auto movedEndpoint = endpointToMove; - int numMoved; - for (numMoved = 0; numMoved < std::abs (count); ++numMoved) + int numMoved = 0; + for (; numMoved < std::abs (count); ++numMoved) { - auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface, - endpointToMove, - boundaryType, - direction); - - if (nextEndpoint == endpointToMove) + auto nextEndpoint = ATH::findTextBoundary (textInterface, + movedEndpoint, + boundaryType, + direction, + ATH::IncludeThisBoundary::no, + direction == ATH::Direction::forwards ? ATH::IncludeWhitespaceAfterWords::yes + : ATH::IncludeWhitespaceAfterWords::no); + + if (nextEndpoint == movedEndpoint) break; - endpointToMove = nextEndpoint; + movedEndpoint = nextEndpoint; } *pRetVal = numMoved; - setEndpointChecked (endpoint, endpointToMove); + + setEndpointChecked (endpoint, movedEndpoint); return S_OK; }); diff --git a/source/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp b/source/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp deleted file mode 100644 index 3b15aa569..000000000 --- a/source/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp +++ /dev/null @@ -1,900 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -//============================================================================== -// This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderCursor.java with min sdk version 16 -// See juce_core/native/java/README.txt on how to generate this byte-code. -static const uint8 javaJuceContentProviderCursor[] = -{31,139,8,8,191,114,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,67,117, -114,115,111,114,46,100,101,120,0,117,147,177,111,211,64,20,198,223,157,157,148,150,54,164,192,208,14,64,144,16,18,67,235,138,2, -75,40,162,10,44,150,65,149,2,25,218,233,176,173,198,37,241,69,182,19,121,96,160,21,136,37,19,98,234,80,85,149,152,88,24,248, -3,24,146,63,130,141,137,129,13,169,99,7,190,203,157,33,18,194,210,207,247,222,229,189,239,157,206,95,130,48,159,91,91,191,75,227, -60,200,143,134,239,247,151,62,189,43,175,127,249,246,235,241,215,241,112,231,231,193,237,135,22,81,143,136,242,214,157,139, -100,158,99,78,84,37,189,95,2,159,129,13,70,128,129,83,179,127,102,242,27,120,157,129,71,224,16,156,128,143,96,12,126,128,69,232, -93,6,75,224,10,184,14,238,129,13,224,130,16,188,4,3,174,245,44,51,79,205,152,53,171,101,206,86,54,241,27,20,206,152,120,136, -248,156,137,63,32,134,12,45,76,206,166,187,148,230,28,169,125,62,201,249,159,156,209,188,201,23,77,93,241,187,122,134,38,40,225, -52,42,124,197,245,252,94,141,104,147,182,113,95,21,76,208,83,222,114,125,86,89,101,168,109,162,162,183,134,46,86,249,71,215, -158,228,54,149,239,71,113,148,61,32,230,210,85,183,239,135,13,25,103,97,156,109,37,114,16,5,97,210,232,39,169,76,86,247,196,64, -208,53,79,196,65,34,163,192,9,68,38,94,136,52,116,158,136,44,137,114,93,84,167,91,158,47,187,78,210,77,59,206,30,164,156,255, -234,213,137,181,136,183,92,178,90,174,135,192,163,75,59,158,154,225,116,68,188,235,52,33,26,239,214,169,228,119,100,26,210,121, -95,118,250,221,248,169,232,134,41,45,251,90,176,217,22,73,33,215,80,101,1,217,109,153,102,52,171,222,207,228,115,52,218,89, -59,74,233,38,191,48,63,83,217,88,161,85,194,178,141,139,224,184,28,190,255,218,30,113,126,192,201,98,223,249,130,185,27,54,181, -22,222,227,83,254,43,60,49,50,235,180,15,11,47,150,167,252,200,106,186,95,121,146,85,255,122,134,215,180,190,242,169,101,106, -212,119,165,154,238,157,124,243,170,142,213,255,224,55,143,234,50,200,64,3,0,0,0,0}; - -// This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderFileObserver.java with min sdk version 16 -// See juce_core/native/java/README.txt on how to generate this byte-code. -static const uint8 javaJuceContentProviderFileObserver[] = -{31,139,8,8,194,122,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,70,105, -108,101,79,98,115,101,114,118,101,114,46,100,101,120,0,133,147,205,107,19,65,24,198,223,249,72,98,171,46,105,235,69,16,201,65,81, -68,221,136,10,66,84,144,250,65,194,130,197,212,32,5,15,155,100,104,182,38,187,97,119,141,241,32,126,30,196,147,23,79,246,216, -131,120,202,77,169,80,212,191,64,193,66,143,30,60,138,255,130,62,179,51,165,219,147,129,223,188,239,188,239,204,179,179,179,79, -186,106,60,93,61,123,158,54,159,255,248,112,97,210,120,124,98,237,251,177,7,109,245,115,253,225,198,159,47,243,171,135,198,130, -104,72,68,227,214,185,89,178,191,45,78,116,128,76,189,8,62,3,169,235,128,129,61,204,204,203,204,204,171,24,142,99,207,2,226, -4,124,4,159,192,6,248,5,254,130,42,250,87,193,13,224,129,91,224,14,184,11,30,129,23,224,21,120,3,222,130,53,240,158,27,125,110, -159,95,176,231,41,233,51,216,249,75,44,152,178,249,107,228,211,54,95,69,190,215,230,239,144,11,40,57,153,150,200,222,81,100, -170,166,190,47,139,68,51,185,200,237,93,8,27,191,218,66,17,138,186,54,225,230,44,195,42,209,149,194,18,238,206,201,58,250,121, -235,182,215,172,160,191,200,137,159,113,172,158,204,246,50,251,62,38,151,89,103,251,29,139,23,131,48,72,47,19,171,19,107,208, -145,198,253,142,154,143,194,84,133,233,66,28,141,130,174,138,175,7,125,117,179,157,168,120,164,226,211,43,254,200,167,131,158, -31,118,227,40,232,186,81,226,230,219,53,114,189,78,52,112,227,65,210,119,87,32,229,254,71,175,70,179,158,150,116,251,126,184, -236,54,211,56,8,151,107,196,90,36,90,117,143,100,171,97,70,175,142,2,134,195,29,35,213,236,249,241,110,161,107,35,148,169,160, -178,32,123,81,146,210,148,30,23,163,219,137,34,57,240,147,123,84,138,66,179,76,14,253,180,71,50,237,5,9,29,21,229,185,153,146, -115,233,20,157,228,206,92,201,89,194,21,113,70,156,61,125,34,191,113,246,12,223,143,253,198,101,237,183,223,133,229,226,182,103, -121,206,183,34,231,93,153,243,111,129,118,60,92,164,29,31,179,138,217,175,189,204,202,102,141,246,24,175,24,125,237,111,97, -215,104,15,80,197,236,205,252,81,54,185,254,255,252,3,243,31,208,130,120,3,0,0,0,0}; - -//============================================================================== -#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, CALLBACK) \ - METHOD (constructor, "", "(Landroid/os/ParcelFileDescriptor;JJ)V") \ - METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \ - METHOD (getLength, "getLength", "()J") - -DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;") - -DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor") -#undef JNI_CLASS_MEMBERS - -//============================================================================== -class AndroidContentSharerCursor -{ -public: - class Owner - { - public: - virtual ~Owner() {} - - virtual void cursorClosed (const AndroidContentSharerCursor&) = 0; - }; - - AndroidContentSharerCursor (Owner& ownerToUse, JNIEnv* env, - const LocalRef& contentProvider, - const LocalRef& resultColumns) - : owner (ownerToUse), - cursor (GlobalRef (LocalRef (env->NewObject (JuceContentProviderCursor, - JuceContentProviderCursor.constructor, - reinterpret_cast (this), - resultColumns.get())))) - { - // the content provider must be created first - jassert (contentProvider.get() != nullptr); - } - - jobject getNativeCursor() { return cursor.get(); } - - void cursorClosed() - { - 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, "", "(J[Ljava/lang/String;)V") \ - CALLBACK (contentSharerCursorClosed, "contentSharerCursorClosed", "(J)V") \ - - DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/rmsl/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor, sizeof (javaJuceContentProviderCursor)) - #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 -{ -public: - class Owner - { - public: - virtual ~Owner() {} - - virtual void fileHandleClosed (const AndroidContentSharerFileObserver&) = 0; - }; - - AndroidContentSharerFileObserver (Owner& ownerToUse, JNIEnv* env, - const LocalRef& contentProvider, - const String& filepathToUse) - : owner (ownerToUse), - filepath (filepathToUse), - fileObserver (GlobalRef (LocalRef (env->NewObject (JuceContentProviderFileObserver, - JuceContentProviderFileObserver.constructor, - reinterpret_cast (this), - javaString (filepath).get(), - open | access | closeWrite | closeNoWrite)))) - { - // the content provider must be created first - jassert (contentProvider.get() != nullptr); - - env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching); - } - - void onFileEvent (int event, const LocalRef& path) - { - ignoreUnused (path); - - if (event == open) - { - ++numOpenedHandles; - } - else if (event == access) - { - fileWasRead = true; - } - else if (event == closeNoWrite || event == closeWrite) - { - --numOpenedHandles; - - // numOpenedHandles may get negative if we don't receive open handle event. - if (fileWasRead && numOpenedHandles <= 0) - { - MessageManager::callAsync ([this] - { - getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching); - owner.fileHandleClosed (*this); - }); - } - } - } - -private: - static constexpr int open = 32; - static constexpr int access = 1; - static constexpr int closeWrite = 8; - static constexpr int closeNoWrite = 16; - - bool fileWasRead = false; - int numOpenedHandles = 0; - - Owner& owner; - String filepath; - GlobalRef fileObserver; - - //============================================================================== - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(JLjava/lang/String;I)V") \ - METHOD (startWatching, "startWatching", "()V") \ - METHOD (stopWatching, "stopWatching", "()V") \ - CALLBACK (contentSharerFileObserverEvent, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \ - - DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/rmsl/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver, sizeof (javaJuceContentProviderFileObserver)) - #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 -{ -public: - AndroidContentSharerPrepareFilesThread (AsyncUpdater& ownerToUse, - const Array& fileUrlsToUse, - const String& packageNameToUse, - const String& uriBaseToUse) - : Thread ("AndroidContentSharerPrepareFilesThread"), - owner (ownerToUse), - fileUrls (fileUrlsToUse), - resultFileUris (GlobalRef (LocalRef (getEnv()->NewObject (JavaArrayList, - JavaArrayList.constructor, - fileUrls.size())))), - packageName (packageNameToUse), - uriBase (uriBaseToUse) - { - startThread(); - } - - ~AndroidContentSharerPrepareFilesThread() override - { - signalThreadShouldExit(); - waitForThreadToExit (10000); - - for (auto& f : temporaryFilesFromAssetFiles) - f.deleteFile(); - } - - jobject getResultFileUris() { return resultFileUris.get(); } - const StringArray& getMimeTypes() const { return mimeTypes; } - const StringArray& getFilePaths() const { return filePaths; } - -private: - struct StreamCloser - { - StreamCloser (const LocalRef& streamToUse) - : stream (GlobalRef (streamToUse)) - { - } - - ~StreamCloser() - { - if (stream.get() != nullptr) - getEnv()->CallVoidMethod (stream, JavaCloseable.close); - } - - GlobalRef stream; - }; - - void run() override - { - auto* env = getEnv(); - - bool canSpecifyMimeTypes = true; - - for (auto f : fileUrls) - { - auto scheme = f.getScheme(); - - // Only "file://" scheme or no scheme (for files in app bundle) are allowed! - jassert (scheme.isEmpty() || scheme == "file"); - - if (scheme.isEmpty()) - { - // Raw resource names need to be all lower case - jassert (f.toString (true).toLowerCase() == f.toString (true)); - - // This will get us a file with file:// URI - f = copyAssetFileToTemporaryFile (env, f.toString (true)); - - if (f.isEmpty()) - continue; - } - - if (threadShouldExit()) - return; - - auto filepath = URL::removeEscapeChars (f.toString (true).fromFirstOccurrenceOf ("file://", false, false)); - - filePaths.add (filepath); - - auto filename = filepath.fromLastOccurrenceOf ("/", false, true); - auto fileExtension = filename.fromLastOccurrenceOf (".", false, true); - auto contentString = uriBase + String (filePaths.size() - 1) + "/" + filename; - - auto uri = LocalRef (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, - javaString (contentString).get())); - - if (canSpecifyMimeTypes) - canSpecifyMimeTypes = fileExtension.isNotEmpty(); - - if (canSpecifyMimeTypes) - mimeTypes.addArray (getMimeTypesForFileExtension (fileExtension)); - else - mimeTypes.clear(); - - env->CallBooleanMethod (resultFileUris, JavaArrayList.add, uri.get()); - } - - owner.triggerAsyncUpdate(); - } - - URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename) - { - 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()); - - // Raw resource not found. Please make sure that you include your file as a raw resource - // and that you specify just the file name, without an extension. - jassert (fileId != 0); - - if (fileId == 0) - return {}; - - auto assetFd = LocalRef (env->CallObjectMethod (resources, - AndroidResources.openRawResourceFd, - fileId)); - - auto inputStream = StreamCloser (LocalRef (env->CallObjectMethod (assetFd, - AssetFileDescriptor.createInputStream))); - - if (jniCheckHasExceptionOccurredAndClear()) - { - // Failed to open file stream for resource - jassertfalse; - return {}; - } - - auto tempFile = File::createTempFile ({}); - tempFile.createDirectory(); - tempFile = tempFile.getChildFile (filename); - - auto outputStream = StreamCloser (LocalRef (env->NewObject (JavaFileOutputStream, - JavaFileOutputStream.constructor, - javaString (tempFile.getFullPathName()).get()))); - - if (jniCheckHasExceptionOccurredAndClear()) - { - // Failed to open file stream for temporary file - jassertfalse; - return {}; - } - - auto buffer = LocalRef (env->NewByteArray (1024)); - int bytesRead = 0; - - for (;;) - { - if (threadShouldExit()) - return {}; - - bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get()); - - if (jniCheckHasExceptionOccurredAndClear()) - { - // Failed to read from resource file. - jassertfalse; - return {}; - } - - if (bytesRead < 0) - break; - - env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead); - - if (jniCheckHasExceptionOccurredAndClear()) - { - // Failed to write to temporary file. - jassertfalse; - return {}; - } - } - - temporaryFilesFromAssetFiles.add (tempFile); - - return URL (tempFile); - } - - AsyncUpdater& owner; - Array fileUrls; - - GlobalRef resultFileUris; - String packageName; - String uriBase; - - StringArray filePaths; - Array temporaryFilesFromAssetFiles; - StringArray mimeTypes; -}; - -//============================================================================== -class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl, - public AndroidContentSharerFileObserver::Owner, - public AndroidContentSharerCursor::Owner, - public AsyncUpdater, - private Timer -{ -public: - ContentSharerNativeImpl (ContentSharer& cs) - : owner (cs), - packageName (juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)))), - uriBase ("content://" + packageName + ".sharingcontentprovider/") - { - } - - ~ContentSharerNativeImpl() override - { - masterReference.clear(); - } - - void shareFiles (const Array& files) override - { - if (! isContentSharingEnabled()) - { - // You need to enable "Content Sharing" in Projucer's Android exporter. - jassertfalse; - owner.sharingFinished (false, {}); - } - - prepareFilesThread.reset (new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase)); - } - - void shareText (const String& text) override - { - if (! isContentSharingEnabled()) - { - // You need to enable "Content Sharing" in Projucer's Android exporter. - jassertfalse; - owner.sharingFinished (false, {}); - } - - auto* env = getEnv(); - - auto intent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructor)); - env->CallObjectMethod (intent, AndroidIntent.setAction, - javaString ("android.intent.action.SEND").get()); - env->CallObjectMethod (intent, AndroidIntent.putExtra, - javaString ("android.intent.extra.TEXT").get(), - javaString (text).get()); - env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get()); - - auto chooserIntent = LocalRef (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser, - intent.get(), javaString ("Choose share target").get())); - - startAndroidActivityForResult (chooserIntent, 1003, - [weakRef = WeakReference { this }] (int /*requestCode*/, - int resultCode, - LocalRef /*intentData*/) mutable - { - if (weakRef != nullptr) - weakRef->sharingFinished (resultCode); - }); - } - - //============================================================================== - void cursorClosed (const AndroidContentSharerCursor& cursor) override - { - cursors.removeObject (&cursor); - } - - void fileHandleClosed (const AndroidContentSharerFileObserver&) override - { - decrementPendingFileCountAndNotifyOwnerIfReady(); - } - - //============================================================================== - jobject openFile (const LocalRef& contentProvider, - const LocalRef& uri, const LocalRef& mode) - { - ignoreUnused (mode); - - WeakReference weakRef (this); - - if (weakRef == nullptr) - return nullptr; - - auto* env = getEnv(); - - auto uriElements = getContentUriElements (env, uri); - - if (uriElements.filepath.isEmpty()) - return nullptr; - - return getAssetFileDescriptor (env, contentProvider, uriElements.filepath); - } - - jobject query (const LocalRef& contentProvider, const LocalRef& uri, - const LocalRef& projection) - { - StringArray requestedColumns = javaStringArrayToJuce (projection); - StringArray supportedColumns = getSupportedColumns(); - - StringArray resultColumns; - - for (const auto& col : supportedColumns) - { - if (requestedColumns.contains (col)) - resultColumns.add (col); - } - - // Unsupported columns were queried, file sharing may fail. - if (resultColumns.isEmpty()) - return nullptr; - - auto resultJavaColumns = juceStringArrayToJava (resultColumns); - - auto* env = getEnv(); - - auto cursor = cursors.add (new AndroidContentSharerCursor (*this, env, contentProvider, - resultJavaColumns)); - - auto uriElements = getContentUriElements (env, uri); - - if (uriElements.filepath.isEmpty()) - return cursor->getNativeCursor(); - - auto values = LocalRef (env->NewObjectArray ((jsize) resultColumns.size(), - JavaObject, nullptr)); - - for (int i = 0; i < resultColumns.size(); ++i) - { - if (resultColumns.getReference (i) == "_display_name") - { - env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get()); - } - else if (resultColumns.getReference (i) == "_size") - { - auto javaFile = LocalRef (env->NewObject (JavaFile, JavaFile.constructor, - javaString (uriElements.filepath).get())); - - jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length); - - env->SetObjectArrayElement (values, i, env->NewObject (JavaLong, - JavaLong.constructor, - fileLength)); - } - } - - cursor->addRow (values); - return cursor->getNativeCursor(); - } - - jobjectArray getStreamTypes (const LocalRef& uri, const LocalRef& mimeTypeFilter) - { - auto* env = getEnv(); - - auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true); - - if (extension.isEmpty()) - return nullptr; - - return juceStringArrayToJava (filterMimeTypes (getMimeTypesForFileExtension (extension), - juceString (mimeTypeFilter.get()))); - } - - void sharingFinished (int resultCode) - { - sharingActivityDidFinish = true; - - succeeded = resultCode == -1; - - // Give content sharer a chance to request file access. - if (nonAssetFilesPendingShare.get() == 0) - startTimer (2000); - else - notifyOwnerIfReady(); - } - -private: - bool isContentSharingEnabled() const - { - auto* env = getEnv(); - - LocalRef packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager)); - - constexpr int getProviders = 8; - auto packageInfo = LocalRef (env->CallObjectMethod (packageManager, - AndroidPackageManager.getPackageInfo, - javaString (packageName).get(), - getProviders)); - auto providers = LocalRef ((jobjectArray) env->GetObjectField (packageInfo, - AndroidPackageInfo.providers)); - - if (providers == nullptr) - return false; - - auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider"; - const int numProviders = env->GetArrayLength (providers.get()); - - for (int i = 0; i < numProviders; ++i) - { - auto providerInfo = LocalRef (env->GetObjectArrayElement (providers, i)); - auto authority = LocalRef ((jstring) env->GetObjectField (providerInfo, - AndroidProviderInfo.authority)); - - if (juceString (authority) == sharingContentProviderAuthority) - return true; - } - - return false; - } - - void handleAsyncUpdate() override - { - jassert (prepareFilesThread != nullptr); - - if (prepareFilesThread == nullptr) - return; - - filesPrepared (prepareFilesThread->getResultFileUris(), prepareFilesThread->getMimeTypes()); - } - - void filesPrepared (jobject fileUris, const StringArray& mimeTypes) - { - auto* env = getEnv(); - - auto intent = LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructor)); - env->CallObjectMethod (intent, AndroidIntent.setAction, - javaString ("android.intent.action.SEND_MULTIPLE").get()); - - env->CallObjectMethod (intent, AndroidIntent.setType, - javaString (getCommonMimeType (mimeTypes)).get()); - - constexpr int grantReadPermission = 1; - env->CallObjectMethod (intent, AndroidIntent.setFlags, grantReadPermission); - - env->CallObjectMethod (intent, AndroidIntent.putParcelableArrayListExtra, - javaString ("android.intent.extra.STREAM").get(), - fileUris); - - auto chooserIntent = LocalRef (env->CallStaticObjectMethod (AndroidIntent, - AndroidIntent.createChooser, - intent.get(), - javaString ("Choose share target").get())); - - startAndroidActivityForResult (chooserIntent, 1003, - [weakRef = WeakReference { this }] (int /*requestCode*/, - int resultCode, - LocalRef /*intentData*/) mutable - { - if (weakRef != nullptr) - weakRef->sharingFinished (resultCode); - }); - } - - void decrementPendingFileCountAndNotifyOwnerIfReady() - { - --nonAssetFilesPendingShare; - - notifyOwnerIfReady(); - } - - void notifyOwnerIfReady() - { - if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0) - owner.sharingFinished (succeeded, {}); - } - - void timerCallback() override - { - stopTimer(); - - notifyOwnerIfReady(); - } - - //============================================================================== - struct ContentUriElements - { - String index; - String filename; - String filepath; - }; - - ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef& uri) const - { - jassert (prepareFilesThread != nullptr); - - if (prepareFilesThread == nullptr) - return {}; - - auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString)); - - auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false) - .upToFirstOccurrenceOf ("/", false, true); - - auto filename = fullUri.fromLastOccurrenceOf ("/", false, true); - - return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] }; - } - - static StringArray getSupportedColumns() - { - return StringArray ("_display_name", "_size"); - } - - jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef& contentProvider, - const String& filepath) - { - // This function can be called from multiple threads. - { - const ScopedLock sl (nonAssetFileOpenLock); - - if (! nonAssetFilePathsPendingShare.contains (filepath)) - { - nonAssetFilePathsPendingShare.add (filepath); - ++nonAssetFilesPendingShare; - - nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env, - contentProvider, - filepath)); - } - } - - auto javaFile = LocalRef (env->NewObject (JavaFile, JavaFile.constructor, - javaString (filepath).get())); - - constexpr int modeReadOnly = 268435456; - auto parcelFileDescriptor = LocalRef (env->CallStaticObjectMethod (ParcelFileDescriptor, - ParcelFileDescriptor.open, - javaFile.get(), modeReadOnly)); - - if (jniCheckHasExceptionOccurredAndClear()) - { - // Failed to create file descriptor. Have you provided a valid file path/resource name? - jassertfalse; - return nullptr; - } - - jlong startOffset = 0; - jlong unknownLength = -1; - - assetFileDescriptors.add (GlobalRef (LocalRef (env->NewObject (AssetFileDescriptor, - AssetFileDescriptor.constructor, - parcelFileDescriptor.get(), - startOffset, unknownLength)))); - - return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get(); - } - - StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter) - { - String filterToUse (filter.removeCharacters ("*")); - - if (filterToUse.isEmpty() || filterToUse == "/") - return mimeTypes; - - StringArray result; - - for (const auto& type : mimeTypes) - if (String (type).contains (filterToUse)) - result.add (type); - - return result; - } - - String getCommonMimeType (const StringArray& mimeTypes) - { - if (mimeTypes.isEmpty()) - return "*/*"; - - auto commonMime = mimeTypes[0]; - bool lookForCommonGroup = false; - - for (int i = 1; i < mimeTypes.size(); ++i) - { - if (mimeTypes[i] == commonMime) - continue; - - if (! lookForCommonGroup) - { - lookForCommonGroup = true; - commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false); - } - - if (! mimeTypes[i].startsWith (commonMime)) - return "*/*"; - } - - return lookForCommonGroup ? commonMime + "*" : commonMime; - } - - ContentSharer& owner; - String packageName; - String uriBase; - - std::unique_ptr prepareFilesThread; - - bool succeeded = false; - String errorDescription; - - bool sharingActivityDidFinish = false; - - OwnedArray cursors; - - Array assetFileDescriptors; - - CriticalSection nonAssetFileOpenLock; - StringArray nonAssetFilePathsPendingShare; - Atomic nonAssetFilesPendingShare { 0 }; - OwnedArray nonAssetFileObservers; - - WeakReference::Master masterReference; - friend class WeakReference; - - //============================================================================== - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[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;") \ - - DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceSharingContentProvider, "com/rmsl/juce/JuceSharingContentProvider", 16) - #undef JNI_CLASS_MEMBERS - - static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection) - { - if (auto *pimpl = (ContentSharer::ContentSharerNativeImpl *) ContentSharer::getInstance ()->pimpl.get ()) - return pimpl->query (LocalRef (static_cast (contentProvider)), - LocalRef (static_cast (uri)), - LocalRef (static_cast (projection))); - - return nullptr; - } - - 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 nullptr; - } - - 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))); - - return nullptr; - } -}; - -//============================================================================== -ContentSharer::Pimpl* ContentSharer::createPimpl() -{ - return new ContentSharerNativeImpl (*this); -} - -ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider_Class ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider; - -} // namespace juce diff --git a/source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp deleted file mode 100644 index 7c9a4e4f6..000000000 --- a/source/modules/juce_gui_basics/native/juce_android_FileChooser.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -class FileChooser::Native : public FileChooser::Pimpl -{ -public: - //============================================================================== - Native (FileChooser& fileChooser, int flags) : owner (fileChooser) - { - if (currentFileChooser == nullptr) - { - currentFileChooser = this; - auto* env = getEnv(); - - auto sdkVersion = getAndroidSDKVersion(); - auto saveMode = ((flags & FileBrowserComponent::saveMode) != 0); - auto selectsDirectories = ((flags & FileBrowserComponent::canSelectDirectories) != 0); - - // You cannot save a directory - jassert (! (saveMode && selectsDirectories)); - - if (sdkVersion < 19) - { - // native save dialogs are only supported in Android versions >= 19 - jassert (! saveMode); - saveMode = false; - } - - if (sdkVersion < 21) - { - // native directory chooser dialogs are only supported in Android versions >= 21 - jassert (! selectsDirectories); - selectsDirectories = false; - } - - const char* action = (selectsDirectories ? "android.intent.action.OPEN_DOCUMENT_TREE" - : (saveMode ? "android.intent.action.CREATE_DOCUMENT" - : (sdkVersion >= 19 ? "android.intent.action.OPEN_DOCUMENT" - : "android.intent.action.GET_CONTENT"))); - - - intent = GlobalRef (LocalRef (env->NewObject (AndroidIntent, AndroidIntent.constructWithString, - javaString (action).get()))); - - if (owner.startingFile != File()) - { - if (saveMode && (! owner.startingFile.isDirectory())) - env->CallObjectMethod (intent.get(), AndroidIntent.putExtraString, - javaString ("android.intent.extra.TITLE").get(), - javaString (owner.startingFile.getFileName()).get()); - - - URL url (owner.startingFile); - LocalRef uri (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, - javaString (url.toString (true)).get())); - - if (uri) - env->CallObjectMethod (intent.get(), AndroidIntent.putExtraParcelable, - javaString ("android.provider.extra.INITIAL_URI").get(), - uri.get()); - } - - - if (! selectsDirectories) - { - env->CallObjectMethod (intent.get(), AndroidIntent.addCategory, - javaString ("android.intent.category.OPENABLE").get()); - - auto mimeTypes = convertFiltersToMimeTypes (owner.filters); - - if (mimeTypes.size() == 1) - { - env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeTypes[0]).get()); - } - else - { - String mimeGroup = "*"; - - if (mimeTypes.size() > 0) - { - mimeGroup = mimeTypes[0].upToFirstOccurrenceOf ("/", false, false); - auto allMimeTypesHaveSameGroup = true; - - LocalRef jMimeTypes (env->NewObjectArray (mimeTypes.size(), JavaString, - javaString("").get())); - - for (int i = 0; i < mimeTypes.size(); ++i) - { - env->SetObjectArrayElement (jMimeTypes.get(), i, javaString (mimeTypes[i]).get()); - - if (mimeGroup != mimeTypes[i].upToFirstOccurrenceOf ("/", false, false)) - allMimeTypesHaveSameGroup = false; - } - - env->CallObjectMethod (intent.get(), AndroidIntent.putExtraStrings, - javaString ("android.intent.extra.MIME_TYPES").get(), - jMimeTypes.get()); - - if (! allMimeTypesHaveSameGroup) - mimeGroup = "*"; - } - - env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeGroup + "/*").get()); - } - } - } - else - jassertfalse; // there can only be a single file chooser - } - - ~Native() override - { - masterReference.clear(); - currentFileChooser = nullptr; - } - - void runModally() override - { - // Android does not support modal file choosers - jassertfalse; - } - - void launch() override - { - auto* env = getEnv(); - - if (currentFileChooser != nullptr) - { - startAndroidActivityForResult (LocalRef (env->NewLocalRef (intent.get())), /*READ_REQUEST_CODE*/ 42, - [myself = WeakReference { this }] (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 onActivityResult (int /*requestCode*/, int resultCode, const LocalRef& intentData) - { - currentFileChooser = nullptr; - auto* env = getEnv(); - - Array chosenURLs; - - if (resultCode == /*Activity.RESULT_OK*/ -1 && intentData != nullptr) - { - LocalRef uri (env->CallObjectMethod (intentData.get(), AndroidIntent.getData)); - - if (uri != nullptr) - { - auto jStr = (jstring) env->CallObjectMethod (uri, JavaObject.toString); - - if (jStr != nullptr) - chosenURLs.add (URL (juceString (env, jStr))); - } - } - - owner.finished (chosenURLs); - } - - static Native* currentFileChooser; - - static StringArray convertFiltersToMimeTypes (const String& fileFilters) - { - StringArray result; - auto wildcards = StringArray::fromTokens (fileFilters, ";", ""); - - for (auto wildcard : wildcards) - { - if (wildcard.upToLastOccurrenceOf (".", false, false) == "*") - { - auto extension = wildcard.fromLastOccurrenceOf (".", false, false); - - result.addArray (getMimeTypesForFileExtension (extension)); - } - } - - result.removeDuplicates (false); - return result; - } - -private: - JUCE_DECLARE_WEAK_REFERENCEABLE (Native) - - FileChooser& owner; - GlobalRef intent; -}; - -FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr; - -std::shared_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, - FilePreviewComponent*) -{ - if (FileChooser::Native::currentFileChooser == nullptr) - return std::make_shared (owner, flags); - - // there can only be one file chooser on Android at a once - jassertfalse; - return nullptr; -} - -bool FileChooser::isPlatformDialogAvailable() -{ - #if JUCE_DISABLE_NATIVE_FILECHOOSERS - return false; - #else - return true; - #endif -} - -} // namespace juce diff --git a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp deleted file mode 100644 index 6f0172b71..000000000 --- a/source/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ /dev/null @@ -1,2108 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ -// This byte-code is generated from native/java/com/rmsl/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,217,126,216,97,0,3,74,97,118,97,68,101,120,66,121,116,101,67,111,100,101,46,100,101,120,0,165,155,11,124,212,213,149,199,207,189,255,255,204,36,147,215,100,18,146,64,18,50,9,175,16,72,102,8,111,19,148,183,6,18,64,18,16,18,171,76,146,127,146, - 129,201,127,134,153,73,72,124,21,149,143,96,173,21,45,82,21,109,177,165,182,110,109,215,90,219,181,22,181,93,93,215,173,186,85,107,87,180,90,31,69,197,150,90,180,86,105,117,117,127,247,49,147,9,143,210,118,195,231,59,231,252,207,125,223,123,238,185,247, - 63,33,221,214,144,59,48,115,54,125,253,172,186,247,86,249,42,166,141,253,112,218,236,231,195,83,47,91,120,183,99,205,99,123,255,208,184,125,22,81,148,136,134,214,207,242,146,254,57,56,147,168,146,41,251,60,240,129,73,180,4,242,121,7,81,9,228,241,12,162, - 203,33,31,200,36,66,18,237,207,33,218,52,153,200,151,75,212,94,78,116,33,184,24,116,130,205,192,6,215,128,107,193,245,96,55,216,3,110,1,251,192,215,192,195,224,5,240,18,248,13,120,3,188,5,126,15,142,129,63,131,156,241,232,7,184,8,12,131,61,224,126,240, - 60,248,12,140,173,32,170,7,231,130,207,129,65,112,19,120,8,60,7,142,130,137,62,162,101,96,8,220,3,14,131,194,74,162,6,208,11,246,130,23,64,69,21,198,2,46,7,119,130,159,129,195,192,152,64,84,14,2,160,13,92,2,110,0,247,129,199,193,155,224,47,160,122,34, - 209,82,208,1,194,224,10,176,3,220,4,110,6,183,130,253,224,0,184,7,220,7,126,12,30,1,143,129,39,192,211,224,57,240,2,120,5,188,14,222,1,71,193,123,224,35,240,9,48,38,17,101,130,28,224,5,227,64,37,152,4,166,130,5,96,45,184,8,108,6,131,224,42,240,37,112, - 27,184,11,220,7,30,6,79,130,151,192,49,112,28,56,176,174,121,160,6,204,6,243,192,74,208,10,58,64,23,8,131,56,216,14,174,7,119,128,123,192,15,193,65,240,34,120,21,188,1,222,1,89,83,136,138,64,57,152,6,230,129,117,32,14,174,0,55,128,187,192,15,193,35,224, - 73,240,34,56,12,254,2,114,171,137,202,192,20,48,15,44,7,171,193,6,96,129,109,96,39,184,29,124,7,252,4,188,8,94,5,111,130,35,128,79,37,42,0,147,192,92,176,28,180,130,16,184,4,92,7,110,3,223,2,7,193,227,224,85,240,14,248,35,120,31,124,10,140,26,248,15, - 152,0,102,129,115,64,11,184,0,92,12,182,128,1,176,27,236,3,7,192,67,224,41,240,75,240,6,56,2,142,129,227,224,99,192,166,17,185,65,33,168,2,53,32,0,230,128,38,112,17,136,130,171,193,126,240,3,240,56,120,21,188,13,62,4,206,233,40,15,42,64,29,152,11,22, - 129,53,224,34,208,3,34,96,24,108,7,95,4,251,192,215,193,207,192,99,224,105,240,30,200,171,197,122,129,82,48,13,204,7,107,65,39,136,130,47,128,59,193,195,224,73,240,18,120,11,124,8,204,58,204,51,152,0,234,65,51,88,11,54,128,77,160,15,216,32,14,134,193, - 213,224,26,112,61,216,11,110,7,223,5,15,128,159,131,23,193,155,224,8,248,61,248,35,248,19,248,8,124,12,62,3,78,196,36,132,40,202,2,69,160,152,84,220,26,11,198,129,82,80,6,16,82,8,97,131,16,22,8,97,128,176,237,9,91,156,176,125,9,219,138,224,254,4,119, - 37,184,28,193,101,8,203,77,88,30,194,244,18,166,133,48,60,66,115,228,7,1,48,3,212,3,132,79,66,88,165,217,96,14,152,171,227,232,124,112,22,104,0,141,96,1,56,27,156,3,22,130,69,96,49,169,88,187,12,156,7,54,128,78,208,5,186,129,69,106,124,201,31,151,150, - 247,151,170,49,51,253,236,214,186,176,139,121,224,218,158,173,245,131,176,231,165,213,229,213,115,246,152,182,231,106,123,158,78,75,234,99,180,238,209,243,42,234,47,208,250,83,186,108,73,90,157,98,174,159,47,85,186,152,227,151,117,158,201,105,245,76, - 213,245,228,107,253,48,244,66,173,31,45,85,109,138,57,255,64,215,35,244,79,116,61,181,186,158,49,122,29,204,50,213,31,177,22,217,101,106,14,235,117,158,38,173,139,182,86,104,189,16,121,86,106,93,180,219,172,117,31,236,45,90,15,64,95,165,245,70,232,171, - 181,126,30,244,53,90,111,131,126,190,214,47,132,222,170,245,238,52,123,56,77,79,64,95,167,245,203,210,236,187,210,244,221,105,250,45,105,117,238,79,179,127,27,122,155,214,239,77,179,31,40,29,209,197,156,175,213,186,152,207,100,61,15,164,229,23,243,185, - 94,235,63,133,253,2,173,63,145,150,231,80,154,254,90,153,242,205,153,122,110,55,106,253,8,236,237,90,63,150,166,127,2,189,67,235,25,226,142,160,117,15,244,207,105,189,76,220,27,180,94,13,253,34,173,7,210,236,194,199,54,105,125,30,236,65,173,159,151,150, - 191,173,92,248,57,163,97,82,50,135,137,125,63,137,226,164,228,191,73,201,232,65,45,15,106,249,144,150,15,107,249,136,206,255,115,18,177,194,71,110,166,100,1,19,113,99,38,253,23,9,89,65,89,76,196,16,149,94,161,211,43,144,82,196,132,207,23,209,86,225,79, - 216,117,15,72,89,65,79,72,89,78,191,148,114,38,189,35,101,22,29,149,62,63,153,150,67,58,16,129,254,68,98,127,58,233,70,41,199,208,126,200,76,68,49,67,202,89,244,33,137,125,94,45,159,179,180,61,11,17,225,35,57,110,245,156,135,118,111,144,178,136,110,210, - 207,183,106,121,167,152,127,157,46,228,110,41,77,186,89,63,239,147,210,160,219,73,237,211,59,180,252,170,148,140,238,210,242,91,36,246,37,167,189,82,78,165,111,146,216,131,147,100,251,5,136,120,191,149,114,62,29,150,178,148,222,38,177,95,107,105,64,203, - 15,72,196,233,57,244,31,36,226,72,54,61,47,229,88,122,159,68,44,81,227,24,139,136,43,100,25,162,230,143,164,172,161,191,74,121,14,153,114,93,2,50,189,28,51,176,67,202,18,218,165,159,175,149,114,30,189,43,215,171,78,230,27,143,26,95,39,177,78,170,92,5, - 236,61,90,246,74,153,71,125,82,142,37,7,83,210,41,215,115,178,204,239,195,10,133,164,244,211,102,41,23,209,22,41,11,40,172,101,191,148,11,201,150,114,44,13,105,121,185,148,115,233,74,41,43,233,58,41,189,116,189,148,57,244,37,41,157,180,71,251,205,94, - 157,254,21,41,103,211,45,82,102,210,215,164,116,211,215,117,190,111,72,153,65,7,164,84,235,32,252,236,110,41,203,232,95,180,252,142,150,247,104,127,252,174,148,227,233,123,218,254,175,186,220,189,90,126,95,203,251,164,244,209,15,164,108,160,251,165,156, - 67,143,105,249,184,148,19,232,5,41,171,232,144,150,47,106,249,146,78,255,181,126,126,89,203,87,164,204,165,223,72,57,133,94,149,114,58,189,38,229,217,244,134,148,202,143,124,218,143,196,243,155,122,255,188,37,165,242,43,145,255,136,148,141,116,76,202, - 122,58,174,229,95,164,156,70,159,72,57,142,62,149,114,6,125,166,159,137,169,124,76,75,206,84,186,193,212,188,20,50,113,110,21,211,109,36,228,98,122,84,238,87,78,63,147,50,159,188,76,156,77,202,95,167,97,103,124,153,196,249,100,208,21,82,114,122,154,196, - 25,85,72,255,78,226,190,48,86,251,171,218,83,201,51,19,175,88,116,33,98,220,21,250,208,26,175,237,226,204,19,49,93,164,39,32,239,208,233,21,186,252,180,180,242,59,144,126,175,78,247,145,58,87,197,153,120,143,46,191,7,242,144,78,23,119,129,169,208,3,245, - 234,190,112,86,189,178,173,134,92,15,12,157,190,9,108,214,121,226,210,110,72,157,251,213,189,161,131,185,41,234,17,55,168,14,158,133,181,204,64,45,162,173,28,191,186,59,121,89,91,167,155,182,122,102,193,158,197,139,240,218,56,131,115,115,62,55,201,229, - 201,167,182,206,44,218,234,171,160,24,118,191,200,215,214,165,242,26,148,101,110,93,51,135,22,15,184,249,124,254,135,207,108,143,24,143,203,83,253,182,169,219,159,224,87,119,152,42,204,104,212,115,153,28,173,104,91,244,187,214,175,238,96,167,107,219, - 160,124,230,245,206,156,50,246,228,54,3,115,105,177,35,27,49,227,24,218,20,113,202,205,108,223,20,68,169,108,86,253,214,201,185,231,201,220,213,239,136,248,107,202,254,44,241,171,181,107,139,170,124,34,165,136,203,123,9,159,143,60,94,196,151,182,173, - 238,244,116,206,118,176,155,93,119,109,115,206,199,200,16,215,143,25,232,175,24,227,106,191,136,247,152,89,111,14,198,184,13,54,47,93,232,201,150,119,180,12,60,137,177,110,240,171,179,182,192,89,72,85,60,27,249,196,44,180,197,115,168,109,102,46,217,158, - 165,200,149,205,230,179,198,17,155,239,60,236,197,145,28,139,144,195,109,172,157,40,222,21,25,125,191,237,70,50,204,217,155,198,209,186,56,230,193,40,34,175,113,33,180,100,254,182,68,14,250,29,192,72,178,77,209,127,83,206,195,108,90,226,16,249,231,27, - 46,154,187,201,65,222,2,81,70,180,85,140,56,103,123,138,196,200,78,104,163,250,191,197,90,10,159,187,218,175,238,116,29,222,92,61,206,42,204,97,212,39,246,82,135,7,20,228,201,49,51,249,143,232,203,126,245,253,67,212,35,110,123,185,41,251,190,148,189, - 94,218,185,190,21,127,195,175,252,185,205,147,39,247,132,240,32,209,238,119,252,234,94,220,230,203,67,31,197,205,27,227,172,244,160,215,101,104,57,55,149,239,190,211,230,43,151,249,114,225,241,98,36,63,70,190,167,132,223,185,188,69,81,95,9,226,111,149, - 153,133,222,56,209,171,142,61,69,120,170,160,104,160,150,246,100,118,236,41,192,83,57,158,38,203,167,66,212,187,16,107,157,109,142,115,68,81,47,86,16,122,133,89,132,114,249,84,102,58,96,75,32,190,187,205,121,230,231,168,194,45,158,241,158,74,237,183, - 123,161,69,113,227,185,112,223,24,104,33,156,77,182,111,179,252,220,130,243,190,10,117,70,61,242,60,54,11,105,194,162,89,52,153,137,27,161,237,51,209,243,54,209,39,7,246,175,207,65,162,70,23,90,245,26,182,103,72,247,228,99,242,78,158,208,51,7,227,233, - 204,40,166,150,12,167,203,91,92,150,145,37,53,59,16,167,54,103,182,49,207,40,36,47,247,78,156,176,116,46,121,29,91,61,131,152,135,108,103,139,211,116,120,199,120,165,180,3,151,210,151,77,225,105,194,187,197,168,84,239,247,80,245,157,57,102,245,7,224, - 125,112,12,188,4,196,225,97,152,242,61,96,228,103,251,57,244,15,61,159,248,163,210,197,93,178,8,251,241,18,82,247,23,110,84,223,202,166,124,133,213,221,204,248,244,59,88,205,62,70,58,186,224,221,47,160,124,166,213,231,149,113,214,148,145,15,239,125,1, - 237,99,129,213,84,105,228,98,207,59,100,188,155,27,80,49,54,186,174,143,42,151,184,165,63,39,203,44,77,166,5,22,163,76,182,244,193,100,90,75,42,237,236,84,154,75,222,254,16,135,117,218,160,179,77,70,6,81,167,67,239,123,43,160,246,76,235,66,111,90,124, - 49,224,139,194,18,13,52,227,118,84,253,215,145,241,244,235,186,196,123,113,59,198,148,171,251,32,126,226,1,21,251,189,158,188,212,158,185,60,144,220,51,133,178,79,73,251,213,218,190,206,51,70,182,207,245,222,187,54,160,222,61,109,207,114,217,87,25,235, - 80,159,151,170,63,53,116,59,55,4,212,57,146,43,203,169,55,211,175,164,217,156,58,126,127,245,12,99,91,161,199,150,204,255,173,51,228,95,153,154,11,213,143,123,79,209,143,7,78,97,123,36,205,102,234,182,30,15,168,239,11,188,76,124,163,208,17,224,164,164, - 65,27,103,32,238,29,219,24,112,209,198,128,83,91,51,225,199,88,73,207,198,128,137,244,12,236,199,177,232,79,21,249,88,190,30,175,88,147,95,5,212,89,125,234,254,183,45,46,162,232,218,86,50,207,175,62,46,124,195,144,254,247,202,105,203,204,92,246,217,103, - 114,220,11,23,144,25,20,101,220,104,71,156,213,191,11,168,239,57,218,182,231,99,157,196,41,229,198,73,55,142,90,175,68,20,241,228,98,215,187,177,251,189,66,154,182,167,64,72,135,141,53,204,164,44,167,107,251,96,70,147,246,17,177,166,110,180,37,124,244, - 163,128,250,174,195,235,105,141,161,39,226,140,101,56,99,153,58,233,166,139,87,62,18,41,54,206,45,85,95,150,144,78,215,86,182,99,208,217,130,249,173,254,67,36,48,150,118,102,226,228,59,98,123,206,194,8,171,197,69,143,171,113,38,191,191,17,178,64,147, - 173,207,218,49,51,212,123,159,151,90,175,66,219,78,217,182,115,62,51,208,242,252,204,76,180,137,158,227,4,247,86,213,87,186,40,178,112,28,221,242,32,90,121,223,246,184,97,207,102,243,152,184,89,40,221,246,13,11,191,117,136,154,68,31,179,68,31,175,100, - 123,84,31,189,158,234,23,212,220,139,245,154,57,67,125,239,48,122,238,213,136,133,77,140,213,73,110,30,93,187,10,171,134,178,127,38,189,163,137,22,205,24,241,169,76,29,59,86,207,80,107,25,217,80,74,109,131,233,181,206,67,155,202,135,99,240,97,81,38,79, - 175,229,38,36,151,161,162,214,103,145,219,16,35,103,134,156,117,150,199,108,79,14,234,119,103,138,181,203,165,44,215,175,110,252,140,166,177,15,165,215,86,191,215,250,156,23,210,246,229,227,140,21,235,44,98,129,88,151,2,33,157,174,103,7,51,214,136,17, - 231,87,191,124,230,156,107,85,206,255,60,115,206,243,145,211,246,136,211,210,157,229,205,159,91,90,67,222,202,9,101,179,113,18,44,193,253,216,59,110,246,131,149,36,106,17,117,124,79,212,225,43,20,146,121,29,75,29,217,142,171,214,61,62,198,246,141,81, - 150,162,165,206,108,231,85,61,143,23,37,235,61,228,118,179,234,215,208,143,171,113,83,199,165,254,135,55,190,238,102,25,243,221,37,244,247,142,96,42,245,124,150,222,159,127,166,39,162,166,234,95,255,227,61,88,163,123,48,233,255,221,131,53,178,7,88,102, - 166,190,29,20,190,37,226,144,95,75,17,191,196,187,91,92,250,30,151,119,150,23,102,168,239,50,209,71,156,251,46,202,230,227,140,255,37,111,225,132,165,56,247,29,157,78,156,251,242,12,143,83,189,41,238,145,46,244,59,7,251,31,124,226,245,78,168,194,169, - 111,136,83,63,3,39,123,139,201,13,113,218,111,229,213,239,230,240,234,163,224,119,224,136,240,247,124,244,173,84,238,93,68,61,62,189,168,166,196,168,174,158,50,189,174,38,45,222,26,245,35,123,195,208,214,172,122,117,7,158,199,93,36,102,196,70,142,92, - 242,46,172,254,88,140,79,197,228,194,122,21,7,196,29,74,220,118,199,241,159,202,59,84,37,90,171,192,251,219,60,68,15,219,115,9,118,168,155,45,96,5,152,109,59,48,137,2,24,241,185,50,191,29,152,72,30,179,109,70,1,110,182,117,178,254,100,138,151,219,129, - 9,228,225,42,205,47,230,247,125,146,191,239,18,63,201,239,92,197,123,206,116,216,102,37,191,60,213,63,11,79,120,94,115,194,179,40,95,72,234,236,205,71,239,152,182,9,124,90,102,232,116,175,62,103,199,105,123,133,150,92,227,211,243,53,141,230,74,123,173, - 182,215,34,58,43,201,100,60,101,250,159,131,70,206,116,174,125,35,121,150,155,41,157,105,187,43,245,93,50,199,76,179,84,126,33,115,82,101,132,116,234,52,39,242,113,109,115,105,153,169,101,242,187,232,92,217,43,210,247,11,49,198,153,122,44,62,105,159, - 169,125,120,102,170,215,170,252,44,45,103,235,114,201,126,10,153,163,219,23,122,94,42,61,47,109,76,57,169,177,22,167,250,161,234,246,232,246,124,169,156,106,92,62,157,155,145,122,223,102,213,132,247,41,103,99,200,14,37,206,38,126,118,3,21,45,137,244, - 71,35,182,101,39,214,88,86,108,125,200,218,86,183,57,56,24,36,182,156,248,242,38,98,77,196,155,166,2,168,43,136,175,104,166,242,21,3,93,214,162,174,46,43,30,15,117,134,194,161,196,240,170,72,183,181,38,22,25,12,117,91,49,42,94,105,13,119,70,130,177,238, - 165,161,120,127,40,30,111,14,197,19,150,141,4,214,76,188,25,181,53,163,154,230,102,50,154,241,128,143,21,226,163,153,138,154,131,118,119,44,18,234,246,7,163,81,255,162,174,68,104,16,53,55,208,172,209,246,104,52,28,234,10,38,66,17,123,98,50,79,115,168, - 199,234,26,238,10,91,75,130,225,112,103,176,107,75,188,129,198,158,174,84,122,82,87,196,70,207,18,254,37,66,14,37,210,147,122,99,193,104,95,168,43,238,95,18,180,7,131,168,112,252,41,146,34,225,72,108,121,40,156,176,98,167,79,111,9,38,98,161,161,6,154, - 250,55,211,71,85,85,114,114,214,53,193,144,141,254,21,159,156,178,214,234,66,66,65,42,33,18,247,47,30,176,187,195,86,3,21,166,27,155,22,135,236,110,81,251,72,29,131,88,106,63,22,107,217,160,37,42,31,55,58,161,37,34,166,75,167,77,29,157,38,156,100,226, - 106,123,121,164,107,32,190,164,47,104,247,90,201,69,78,239,74,42,107,250,144,82,198,115,99,145,129,104,3,205,57,57,165,45,102,89,171,59,227,86,108,208,138,161,149,115,195,145,206,96,184,57,56,28,25,72,140,52,83,241,183,203,53,208,140,209,25,130,233,254, - 234,31,229,189,45,65,59,216,43,138,212,255,221,69,132,195,55,217,61,145,147,250,127,134,50,201,77,210,64,117,163,203,133,236,232,64,162,223,74,244,69,186,253,139,131,113,84,142,103,248,165,141,229,149,94,59,233,244,249,151,117,135,18,145,152,234,78,205, - 233,179,157,84,101,237,25,242,182,72,61,53,59,231,52,119,69,250,253,177,254,120,216,191,25,1,192,127,82,216,152,248,55,227,66,3,45,63,99,5,167,137,28,19,71,175,236,252,127,182,158,6,170,60,83,209,6,170,106,238,14,134,7,67,91,252,65,219,142,36,100,204, - 240,47,179,187,194,145,120,200,238,93,18,14,198,101,48,56,57,79,19,38,54,166,211,43,79,145,222,98,245,119,234,12,22,178,148,159,34,75,107,168,215,14,38,6,98,150,216,48,34,6,251,195,216,91,126,236,176,88,171,181,117,192,178,187,144,146,159,158,162,154, - 171,74,51,53,133,195,86,111,48,172,150,97,217,80,151,21,85,139,61,241,20,121,98,189,3,253,24,123,90,174,130,244,92,8,138,189,106,210,70,140,171,34,173,3,93,125,202,51,210,202,121,211,178,172,238,220,44,99,82,121,154,173,213,234,26,136,193,33,78,83,164, - 21,49,208,238,21,30,57,98,139,89,61,97,212,131,110,12,70,84,232,110,11,198,122,173,244,222,142,59,69,118,213,181,6,26,163,210,6,18,161,176,127,81,44,22,28,22,78,208,64,121,105,102,97,33,207,9,134,6,50,219,54,174,89,70,217,233,62,71,108,61,241,245,77, - 228,88,223,132,31,168,43,200,185,126,69,211,242,229,120,155,135,20,9,43,68,2,30,196,193,182,126,69,59,242,8,69,28,110,235,165,169,185,29,169,205,237,56,250,214,183,163,112,187,172,136,181,147,209,46,202,225,163,89,168,205,228,104,95,33,116,19,2,199,101, - 187,176,226,152,116,182,55,75,179,67,72,216,59,112,54,119,52,145,183,227,100,103,40,232,56,197,90,184,85,92,154,24,8,4,82,250,140,52,189,62,77,159,153,166,207,74,211,103,167,233,115,210,244,185,105,250,60,232,89,74,95,30,14,246,198,41,103,84,64,164,194, - 224,41,2,47,57,131,50,34,137,146,66,54,7,59,173,48,101,4,245,241,78,99,131,221,221,167,62,6,40,51,168,125,56,78,172,147,242,197,225,191,120,32,145,136,216,107,98,104,198,234,38,103,103,4,143,253,144,242,80,36,103,151,60,207,201,213,37,143,173,110,114, - 224,222,16,140,81,86,151,8,85,17,156,178,139,18,226,33,117,50,83,174,124,104,139,5,237,120,79,36,214,79,57,226,214,128,227,56,46,115,163,34,117,121,64,69,145,1,60,151,118,197,172,96,226,228,48,40,194,51,153,221,161,158,30,98,22,57,44,113,174,146,175, - 7,135,242,41,179,198,23,15,183,137,90,51,69,14,121,206,146,163,71,138,236,158,145,83,183,155,178,228,147,136,92,77,221,84,138,237,49,170,182,229,105,137,101,39,38,142,186,180,101,202,84,185,10,121,41,181,37,24,223,130,54,198,8,195,200,13,74,223,150,40, - 7,102,17,154,176,78,86,44,78,25,226,81,184,32,185,133,166,51,101,139,29,43,198,218,22,234,183,100,43,231,89,161,222,190,4,21,64,149,167,76,122,31,133,177,89,239,246,213,118,43,166,210,178,101,21,216,213,193,86,68,73,85,133,218,227,178,163,114,17,16,86, - 229,220,103,143,24,80,153,11,79,107,131,219,54,36,149,141,148,37,148,72,36,33,90,35,15,30,90,135,225,71,253,173,216,227,161,46,139,114,97,89,103,135,132,39,136,113,201,222,156,120,175,144,163,94,31,74,57,180,40,115,1,150,40,178,173,45,178,5,157,45,75, - 61,203,76,97,11,71,80,52,28,28,94,30,11,98,252,38,82,55,200,207,141,196,250,168,4,139,8,167,28,181,42,231,69,68,43,121,58,37,26,93,19,28,16,126,236,73,25,214,90,113,120,124,202,178,56,229,242,148,163,44,56,252,150,70,182,97,55,165,30,215,69,169,48,245, - 32,15,198,243,66,221,221,232,173,110,166,37,130,54,100,153,81,134,88,176,55,89,167,52,160,26,93,167,188,142,82,129,126,176,98,98,99,104,239,201,232,11,198,149,191,22,247,193,179,90,35,61,122,153,99,145,126,53,49,200,130,210,210,187,205,190,8,66,48,11, - 145,27,222,177,90,70,245,56,25,161,254,126,202,19,239,39,161,96,120,73,48,26,111,193,130,80,142,54,180,90,225,101,118,119,42,29,143,240,139,24,54,138,188,190,180,13,71,45,202,150,234,197,234,42,67,25,104,108,125,48,60,128,189,31,194,81,178,197,66,99, - 241,38,59,158,8,226,56,69,106,124,117,52,136,179,149,198,134,226,109,17,156,109,203,134,162,216,239,210,5,151,217,65,172,96,55,234,142,235,213,36,215,22,107,120,137,232,79,241,150,211,188,229,228,36,19,90,251,196,140,58,194,50,154,229,192,7,172,152,232, - 222,42,220,55,200,12,91,61,9,114,134,45,187,55,209,71,78,221,85,102,147,105,11,63,113,217,214,182,85,66,201,176,147,161,35,219,78,223,174,206,72,167,136,65,100,70,194,221,125,242,115,27,229,71,236,228,27,209,18,25,131,176,159,70,76,75,173,120,34,22,25, - 22,142,51,98,212,206,149,86,50,233,93,165,35,166,214,224,160,149,156,47,181,7,211,242,203,201,31,93,69,107,34,18,141,194,84,140,80,33,251,113,194,197,19,157,183,225,91,219,40,39,146,254,6,65,185,145,81,49,158,178,35,182,220,12,50,124,80,102,196,78,58, - 118,142,84,91,6,194,137,80,84,44,137,124,132,115,102,136,163,67,22,69,142,214,208,37,86,50,72,162,38,181,180,178,38,103,68,45,184,75,201,139,81,110,0,81,63,129,24,230,136,74,199,118,71,131,49,228,148,97,34,39,58,202,189,29,81,25,245,203,163,145,232,64, - 248,180,113,158,197,200,21,83,239,164,84,21,179,122,133,111,196,78,255,186,74,101,49,171,31,67,85,195,95,109,159,112,200,57,98,50,98,26,113,43,65,185,113,17,91,83,47,139,148,141,103,57,137,194,83,169,56,253,169,73,13,90,238,30,81,44,237,78,47,139,53, - 39,61,146,74,240,116,202,183,57,26,19,79,6,201,117,161,180,168,87,122,74,179,184,76,7,113,122,198,85,216,148,14,156,19,31,21,46,221,201,199,176,234,211,5,161,112,120,85,36,33,221,33,59,142,13,147,12,80,40,136,167,84,244,64,102,225,104,170,95,184,42,34, - 25,94,54,242,88,18,87,189,105,26,105,75,143,212,148,199,144,153,232,11,197,201,41,62,39,6,180,156,1,171,56,148,12,212,4,85,76,68,198,64,162,103,158,140,253,108,144,28,131,50,106,184,164,88,221,67,166,120,75,162,60,241,153,238,92,153,194,208,22,89,23, - 183,200,51,120,210,105,49,24,138,37,6,130,97,125,190,185,7,71,166,130,109,35,54,68,124,40,0,102,128,122,48,19,204,34,54,76,223,52,57,237,229,174,220,246,90,186,199,100,183,112,87,57,207,122,150,15,149,111,54,232,151,172,228,141,149,244,144,201,175,229, - 176,231,210,163,38,187,142,185,202,159,228,151,148,191,111,208,245,172,166,246,38,7,209,115,166,113,11,143,111,115,229,62,90,65,191,229,140,80,85,35,253,142,147,107,122,135,193,223,103,57,215,26,236,93,86,80,55,244,145,65,215,48,190,185,145,229,231,135, - 26,121,188,220,65,141,44,203,137,242,92,117,128,207,217,200,87,110,171,165,95,112,118,187,104,237,68,121,8,85,230,222,79,175,40,209,104,92,205,126,207,126,204,92,211,249,179,212,192,63,101,219,248,51,108,219,16,127,227,210,215,118,49,238,112,47,170,109, - 172,107,108,60,187,195,160,237,204,125,153,193,174,100,179,27,239,170,52,140,95,176,0,43,30,19,24,111,240,167,24,103,249,197,14,206,207,71,87,28,204,97,56,221,124,218,1,135,219,73,78,230,228,78,163,166,134,15,78,119,240,26,30,159,78,115,85,23,230,242, - 219,249,29,82,49,133,242,85,254,181,209,214,148,226,72,38,239,23,143,215,137,46,127,161,140,6,33,86,210,97,131,239,227,119,10,251,235,38,12,180,75,76,25,189,100,136,207,47,152,124,224,82,200,29,38,27,128,120,215,16,2,37,111,150,207,215,149,209,167,134, - 202,176,91,101,56,46,51,208,141,6,59,138,133,89,185,178,182,125,101,123,29,125,158,93,38,75,221,107,240,27,249,143,24,10,150,214,210,219,140,237,130,186,139,21,229,249,104,15,231,79,136,199,242,157,124,76,57,239,47,231,121,13,117,43,45,62,176,226,66, - 62,188,130,30,228,198,19,108,135,76,231,249,215,240,88,249,222,142,205,187,12,199,110,206,253,214,57,116,175,46,155,187,211,87,182,151,238,54,28,151,63,205,190,196,63,100,151,160,35,251,13,243,54,126,55,123,129,253,15,210,23,236,108,167,91,84,171,59, - 249,19,84,206,31,252,124,249,74,35,51,206,155,13,215,245,220,224,143,210,70,190,112,58,43,200,11,40,60,205,70,214,51,140,47,104,52,178,143,177,57,11,24,51,220,15,50,94,203,202,114,206,113,184,29,217,51,28,89,155,157,238,58,86,80,196,47,109,104,116,102, - 47,96,229,99,132,125,180,145,47,103,229,185,244,136,193,110,197,12,251,12,182,139,7,152,183,136,103,79,199,40,43,12,218,201,106,199,59,72,42,211,38,56,232,245,64,13,253,196,96,207,161,139,244,170,193,134,92,185,161,50,58,206,216,13,40,252,152,65,7,89, - 121,221,230,149,67,155,198,238,36,94,201,110,228,37,62,94,197,35,166,247,25,86,156,195,39,194,80,230,42,102,197,11,139,51,139,151,22,243,226,154,98,83,229,170,144,185,224,143,197,231,166,242,231,242,9,34,63,43,25,175,20,94,82,94,82,65,156,153,238,221, - 62,230,157,124,229,118,243,80,217,20,118,188,140,177,67,229,140,125,0,142,141,71,50,203,230,108,183,111,218,246,237,230,193,138,233,236,181,10,50,156,148,43,74,48,111,45,202,28,168,100,187,124,207,139,143,163,226,99,71,21,227,183,131,199,170,200,89,82, - 154,239,133,135,123,213,191,153,200,124,180,10,89,30,152,140,143,103,196,199,17,241,177,99,10,62,246,139,143,131,83,204,171,56,49,32,164,75,235,127,47,133,192,13,206,98,135,166,96,201,171,25,59,80,109,176,7,170,139,216,51,208,143,128,47,78,101,108,63, - 184,31,28,6,123,106,24,251,54,56,8,158,1,7,166,97,232,224,139,211,25,123,13,188,92,203,216,243,117,204,220,237,103,230,1,63,210,252,14,182,127,22,99,247,204,230,236,167,224,240,108,79,218,239,7,146,50,249,119,57,226,251,234,228,223,230,136,239,177,147, - 127,159,147,252,93,169,248,27,29,241,29,118,242,239,116,156,52,242,183,58,134,71,127,191,157,139,58,125,234,247,66,27,160,59,125,42,143,248,255,103,204,163,236,226,255,156,113,159,106,87,252,109,143,161,243,139,255,247,101,250,212,239,37,166,214,171, - 95,84,136,178,242,255,173,121,84,95,197,223,17,253,31,67,245,215,66,128,52,0,0,0,0 }; - -//============================================================================== -#if JUCE_PUSH_NOTIFICATIONS && JUCE_MODULE_AVAILABLE_juce_gui_extra - extern bool juce_handleNotificationIntent (void*); - extern void juce_firebaseDeviceNotificationsTokenRefreshed (void*); - extern void juce_firebaseRemoteNotificationReceived (void*); - extern void juce_firebaseRemoteMessagesDeleted(); - extern void juce_firebaseRemoteMessageSent(void*); - extern void juce_firebaseRemoteMessageSendError (void*, void*); -#endif - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (create, "", "(II)V") - -DECLARE_JNI_CLASS (AndroidLayoutParams, "android/view/ViewGroup$LayoutParams") -#undef JNI_CLASS_MEMBERS - -#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") - -DECLARE_JNI_CLASS (AndroidViewManager, "android/view/ViewManager") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (create, "", "(IIIIIII)V") \ - FIELD (gravity, "gravity", "I") \ - FIELD (windowAnimations, "windowAnimations", "I") - -DECLARE_JNI_CLASS (AndroidWindowManagerLayoutParams, "android/view/WindowManager$LayoutParams") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getDisplayCutout, "getDisplayCutout", "()Landroid/view/DisplayCutout;") - - DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidWindowInsets, "android/view/WindowInsets", 28) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getSafeInsetBottom, "getSafeInsetBottom", "()I") \ - METHOD (getSafeInsetLeft, "getSafeInsetLeft", "()I") \ - METHOD (getSafeInsetRight, "getSafeInsetRight", "()I") \ - METHOD (getSafeInsetTop, "getSafeInsetTop", "()I") - - DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidDisplayCutout, "android/view/DisplayCutout", 28) -#undef JNI_CLASS_MEMBERS - -//============================================================================== -namespace -{ - enum - { - SYSTEM_UI_FLAG_VISIBLE = 0, - SYSTEM_UI_FLAG_LOW_PROFILE = 1, - SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2, - SYSTEM_UI_FLAG_FULLSCREEN = 4, - SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 512, - SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 1024, - SYSTEM_UI_FLAG_IMMERSIVE = 2048, - SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 4096 - }; - - constexpr int fullScreenFlags = SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - constexpr int FLAG_NOT_FOCUSABLE = 0x8; -} - -//============================================================================== -static bool supportsDisplayCutout() -{ - return getAndroidSDKVersion() >= 28; -} - -static BorderSize androidDisplayCutoutToBorderSize (LocalRef displayCutout, double displayScale) -{ - if (displayCutout.get() == nullptr) - return {}; - - auto* env = getEnv(); - - auto getInset = [&] (jmethodID methodID) - { - return roundToInt (env->CallIntMethod (displayCutout.get(), methodID) / displayScale); - }; - - return { getInset (AndroidDisplayCutout.getSafeInsetTop), - getInset (AndroidDisplayCutout.getSafeInsetLeft), - getInset (AndroidDisplayCutout.getSafeInsetBottom), - getInset (AndroidDisplayCutout.getSafeInsetRight) }; -} - -/* The usage of the KeyPress class relies on its keyCode member having the standard ASCII values - represent ASCII keycodes. However in the native Android keycodes the values for special keys - e.g. RETURN, F1-F12 overlap with the ASCII range. Hence we need to translate them. -*/ -static constexpr int translateAndroidKeyCode (int keyCode) noexcept -{ - switch (keyCode) - { - case 7: return '0'; - case 8: return '1'; - case 9: return '2'; - case 10: return '3'; - case 11: return '4'; - case 12: return '5'; - case 13: return '6'; - case 14: return '7'; - case 15: return '8'; - case 16: return '9'; - case 17: return '*'; - case 18: return '#'; - case 19: return KeyPress::upKey; // KEYCODE_DPAD_UP - case 20: return KeyPress::downKey; // KEYCODE_DPAD_DOWN - case 21: return KeyPress::leftKey; // KEYCODE_DPAD_LEFT - case 22: return KeyPress::rightKey; // KEYCODE_DPAD_RIGHT - case 29: return 'A'; - case 30: return 'B'; - case 31: return 'C'; - case 32: return 'D'; - case 33: return 'E'; - case 34: return 'F'; - case 35: return 'G'; - case 36: return 'H'; - case 37: return 'I'; - case 38: return 'J'; - case 39: return 'K'; - case 40: return 'L'; - case 41: return 'M'; - case 42: return 'N'; - case 43: return 'O'; - case 44: return 'P'; - case 45: return 'Q'; - case 46: return 'R'; - case 47: return 'S'; - case 48: return 'T'; - case 49: return 'U'; - case 50: return 'V'; - case 51: return 'W'; - case 52: return 'X'; - case 53: return 'Y'; - case 54: return 'Z'; - case 55: return ','; - case 56: return '.'; - case 61: return KeyPress::tabKey; // KEYCODE_TAB - case 62: return KeyPress::spaceKey; // KEYCODE_SPACE - case 66: return KeyPress::returnKey; // KEYCODE_ENTER - case 67: return KeyPress::backspaceKey; // KEYCODE_DEL - case 68: return '`'; - case 69: return '-'; - case 70: return '='; - case 71: return '['; - case 72: return ']'; - case 73: return '\\'; - case 74: return ';'; - case 75: return '\''; - case 76: return '/'; - case 77: return '@'; - case 81: return '+'; - case 85: return KeyPress::playKey; // KEYCODE_MEDIA_PLAY_PAUSE - case 86: return KeyPress::stopKey; // KEYCODE_MEDIA_STOP - case 87: return KeyPress::fastForwardKey; // KEYCODE_MEDIA_NEXT - case 88: return KeyPress::rewindKey; // KEYCODE_MEDIA_PREVIOUS - case 92: return KeyPress::pageUpKey; // KEYCODE_PAGE_UP - case 93: return KeyPress::pageDownKey; // KEYCODE_PAGE_DOWN - case 111: return KeyPress::escapeKey; // KEYCODE_ESCAPE - case 112: return KeyPress::deleteKey; // KEYCODE_FORWARD_DEL - case 122: return KeyPress::homeKey; // KEYCODE_MOVE_HOME - case 123: return KeyPress::endKey; // KEYCODE_MOVE_END - case 124: return KeyPress::insertKey; // KEYCODE_INSERT - case 131: return KeyPress::F1Key; // KEYCODE_F1 - case 132: return KeyPress::F2Key; // KEYCODE_F2 - case 133: return KeyPress::F3Key; // KEYCODE_F3 - case 134: return KeyPress::F4Key; // KEYCODE_F4 - case 135: return KeyPress::F5Key; // KEYCODE_F5 - case 136: return KeyPress::F6Key; // KEYCODE_F6 - case 137: return KeyPress::F7Key; // KEYCODE_F7 - case 138: return KeyPress::F8Key; // KEYCODE_F8 - case 139: return KeyPress::F9Key; // KEYCODE_F9 - case 140: return KeyPress::F10Key; // KEYCODE_F10 - case 141: return KeyPress::F11Key; // KEYCODE_F11 - case 142: return KeyPress::F12Key; // KEYCODE_F12 - case 144: return '0'; - case 145: return '1'; - case 146: return '2'; - case 147: return '3'; - case 148: return '4'; - case 149: return '5'; - case 150: return '6'; - case 151: return '7'; - case 152: return '8'; - case 153: return '9'; - case 154: return '/'; - case 155: return '*'; - case 156: return '-'; - case 157: return '+'; - case 158: return '.'; - case 159: return ','; - case 161: return '='; - case 162: return '('; - case 163: return ')'; - - default: return 0; - } -} - -static constexpr int translateAndroidKeyboardFlags (int javaFlags) noexcept -{ - constexpr int metaShiftOn = 0x1; - constexpr int metaAltOn = 0x02; - constexpr int metaCtrlOn = 0x1000; - - int flags = 0; - - if ((javaFlags & metaShiftOn) != 0) flags |= ModifierKeys::shiftModifier; - if ((javaFlags & metaAltOn) != 0) flags |= ModifierKeys::altModifier; - if ((javaFlags & metaCtrlOn) != 0) flags |= ModifierKeys::ctrlModifier; - - return flags; -} - -//============================================================================== -class AndroidComponentPeer : public ComponentPeer, - private Timer -{ -public: - AndroidComponentPeer (Component& comp, int windowStyleFlags, void* nativeViewHandle) - : ComponentPeer (comp, windowStyleFlags) - { - 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 (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()); - - auto physicalBounds = (comp.getBoundsInParent().toFloat() * scale).toNearestInt(); - - 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 | FLAG_NOT_FOCUSABLE, - 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 */); - - if (supportsDisplayCutout()) - { - jfieldID layoutInDisplayCutoutModeFieldId = env->GetFieldID (AndroidWindowManagerLayoutParams, - "layoutInDisplayCutoutMode", - "I"); - - if (layoutInDisplayCutoutModeFieldId != nullptr) - env->SetIntField (windowLayoutParams.get(), - layoutInDisplayCutoutModeFieldId, - LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); - } - - if (Desktop::getInstance().getKioskModeComponent() != nullptr) - setNavBarsHidden (true); - - LocalRef activity (getCurrentActivity()); - - if (activity == nullptr) - activity = getMainActivity(); - - viewGroup = GlobalRef (LocalRef (env->CallObjectMethod (activity.get(), AndroidContext.getSystemService, javaString ("window").get()))); - env->CallVoidMethod (viewGroup.get(), AndroidViewManager.addView, view.get(), windowLayoutParams.get()); - } - - if (supportsDisplayCutout()) - { - jmethodID setOnApplyWindowInsetsListenerMethodId = env->GetMethodID (AndroidView, - "setOnApplyWindowInsetsListener", - "(Landroid/view/View$OnApplyWindowInsetsListener;)V"); - - if (setOnApplyWindowInsetsListenerMethodId != nullptr) - env->CallVoidMethod (view.get(), setOnApplyWindowInsetsListenerMethodId, - CreateJavaInterface (new ViewWindowInsetsListener, - "android/view/View$OnApplyWindowInsetsListener").get()); - } - - if (isFocused()) - handleFocusGain(); - } - - ~AndroidComponentPeer() override - { - stopTimer(); - - auto* env = getEnv(); - - env->CallVoidMethod (view, ComponentPeerView.clear); - frontWindow = nullptr; - - GlobalRef localView (view); - GlobalRef localViewGroup (viewGroup); - - callOnMessageThread ([env, localView, localViewGroup] - { - if (env->IsInstanceOf (localViewGroup.get(), AndroidActivity)) - env->CallVoidMethod (localViewGroup.get(), AndroidActivity.setContentView, nullptr); - else - env->CallVoidMethod (localViewGroup.get(), AndroidViewManager.removeView, localView.get()); - }); - } - - void* getNativeHandle() const override - { - return (void*) view.get(); - } - - void setVisible (bool shouldBeVisible) override - { - GlobalRef localView (view); - - callOnMessageThread ([localView, shouldBeVisible] - { - localView.callVoidMethod (ComponentPeerView.setVisible, shouldBeVisible); - }); - } - - void setTitle (const String& title) override - { - view.callVoidMethod (ComponentPeerView.setViewName, javaString (title).get()); - } - - void setBounds (const Rectangle& userRect, bool isNowFullScreen) override - { - auto bounds = (userRect.toFloat() * scale).toNearestInt(); - - if (MessageManager::getInstance()->isThisTheMessageThread()) - { - fullScreen = isNowFullScreen; - - view.callVoidMethod (AndroidView.layout, - bounds.getX(), bounds.getY(), bounds.getRight(), bounds.getBottom()); - - if (viewGroup != nullptr && viewGroupIsWindow) - { - auto* env = getEnv(); - - LocalRef windowLayoutParams (env->NewObject (AndroidWindowManagerLayoutParams, AndroidWindowManagerLayoutParams.create, - bounds.getWidth(), bounds.getHeight(), bounds.getX(), bounds.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->CallVoidMethod (viewGroup.get(), AndroidViewManager.updateViewLayout, view.get(), windowLayoutParams.get()); - } - } - else - { - GlobalRef localView (view); - - MessageManager::callAsync ([localView, bounds] - { - localView.callVoidMethod (AndroidView.layout, - bounds.getX(), bounds.getY(), bounds.getRight(), bounds.getBottom()); - }); - } - } - - Rectangle getBounds() const override - { - Rectangle bounds (view.callIntMethod (AndroidView.getLeft), - view.callIntMethod (AndroidView.getTop), - view.callIntMethod (AndroidView.getWidth), - view.callIntMethod (AndroidView.getHeight)); - - return (bounds.toFloat() / scale).toNearestInt(); - } - - void handleScreenSizeChange() override - { - ComponentPeer::handleScreenSizeChange(); - - if (isFullScreen()) - setFullScreen (true); - } - - Point getScreenPosition() const - { - auto* env = getEnv(); - - LocalRef position (env->NewIntArray (2)); - env->CallVoidMethod (view.get(), AndroidView.getLocationOnScreen, position.get()); - - jint* const screenPosition = env->GetIntArrayElements (position.get(), nullptr); - Point pos (screenPosition[0], screenPosition[1]); - env->ReleaseIntArrayElements (position.get(), screenPosition, 0); - - return pos; - } - - Point localToGlobal (Point relativePosition) override - { - return relativePosition + (getScreenPosition().toFloat() / scale); - } - - using ComponentPeer::localToGlobal; - - Point globalToLocal (Point screenPosition) override - { - return screenPosition - (getScreenPosition().toFloat() / scale); - } - - using ComponentPeer::globalToLocal; - - void setMinimised (bool /*shouldBeMinimised*/) override - { - // n/a - } - - bool isMinimised() const override - { - return false; - } - - void setFullScreen (bool shouldBeFullScreen) override - { - if (shouldNavBarsBeHidden (shouldBeFullScreen)) - { - if (isTimerRunning()) - return; - - startTimer (500); - } - else - { - setNavBarsHidden (false); - } - - auto newBounds = [&] - { - if (navBarsHidden || shouldBeFullScreen) - if (auto* display = Desktop::getInstance().getDisplays().getPrimaryDisplay()) - return navBarsHidden ? display->totalArea - : display->userArea; - - return lastNonFullscreenBounds.isEmpty() ? getBounds() : lastNonFullscreenBounds; - }(); - - if (! newBounds.isEmpty()) - setBounds (newBounds, shouldBeFullScreen); - - component.repaint(); - } - - bool isFullScreen() const override - { - return fullScreen; - } - - void setIcon (const Image& /*newIcon*/) override - { - // n/a - } - - bool contains (Point localPos, bool trueIfInAChildWindow) const override - { - return isPositiveAndBelow (localPos.x, component.getWidth()) - && isPositiveAndBelow (localPos.y, component.getHeight()) - && ((! trueIfInAChildWindow) || view.callBooleanMethod (ComponentPeerView.containsPoint, - (float) localPos.x * scale, - (float) localPos.y * scale)); - } - - OptionalBorderSize getFrameSizeIfPresent() const override - { - // TODO - return {}; - } - - BorderSize getFrameSize() const override - { - // TODO - return {}; - } - - bool setAlwaysOnTop (bool /*alwaysOnTop*/) override - { - // TODO - return false; - } - - void toFront (bool makeActive) override - { - // Avoid calling bringToFront excessively: it's very slow - if (frontWindow != this) - { - view.callVoidMethod (AndroidView.bringToFront); - frontWindow = this; - } - - if (makeActive) - grabFocus(); - - handleBroughtToFront(); - } - - void toBehind (ComponentPeer*) override - { - // TODO - } - - //============================================================================== - void handleMouseDownCallback (int index, Point sysPos, int64 time) - { - lastMousePos = sysPos / scale; - auto 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(), - MouseInputSource::defaultPressure, - MouseInputSource::defaultOrientation, - time, - {}, - index); - - if (isValidPeer (this)) - handleMouseDragCallback (index, sysPos, time); - } - - void handleMouseDragCallback (int index, Point sysPos, int64 time) - { - lastMousePos = sysPos / scale; - auto pos = globalToLocal (lastMousePos); - - jassert (index < 64); - touchesDown = (touchesDown | (1 << (index & 63))); - - ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); - - handleMouseEvent (MouseInputSource::InputSourceType::touch, - pos, - ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier), - MouseInputSource::defaultPressure, - MouseInputSource::defaultOrientation, - time, - {}, - index); - } - - void handleMouseUpCallback (int index, Point sysPos, int64 time) - { - lastMousePos = sysPos / scale; - auto pos = globalToLocal (lastMousePos); - - jassert (index < 64); - touchesDown = (touchesDown & ~(1 << (index & 63))); - - if (touchesDown == 0) - ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons(); - - handleMouseEvent (MouseInputSource::InputSourceType::touch, - pos, - ModifierKeys::currentModifiers.withoutMouseButtons(), - MouseInputSource::defaultPressure, - MouseInputSource::defaultOrientation, - time, - {}, - index); - } - - void handleAccessibilityHoverCallback (int command, Point sysPos, int64) - { - enum - { - TYPE_VIEW_HOVER_ENTER = 0x00000080, - TYPE_VIEW_HOVER_EXIT = 0x00000100, - - ACTION_HOVER_ENTER = 0x00000009, - ACTION_HOVER_MOVE = 0x00000007, - ACTION_HOVER_EXIT = 0x0000000a - }; - - if (auto* topHandler = component.getAccessibilityHandler()) - { - if (auto* virtualHandler = topHandler->getChildAt ((sysPos / scale).roundToInt())) - { - switch (command) - { - case ACTION_HOVER_ENTER: - case ACTION_HOVER_MOVE: - sendAccessibilityEventImpl (*virtualHandler, TYPE_VIEW_HOVER_ENTER, 0); - break; - - case ACTION_HOVER_EXIT: - sendAccessibilityEventImpl (*virtualHandler, TYPE_VIEW_HOVER_EXIT, 0); - break; - } - } - } - } - - void handleKeyDownCallback (int k, int kc, int kbFlags) - { - ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withOnlyMouseButtons() - .withFlags (translateAndroidKeyboardFlags (kbFlags)); - handleKeyPress (translateAndroidKeyCode (k), static_cast (kc)); - } - - void handleKeyUpCallback (int /*k*/, int /*kc*/) - { - } - - void handleBackButtonCallback() - { - bool handled = false; - - if (auto* app = JUCEApplicationBase::getInstance()) - handled = app->backButtonPressed(); - - if (isKioskModeComponent()) - setNavBarsHidden (navBarsHidden); - - if (! handled) - { - auto* env = getEnv(); - LocalRef activity (getCurrentActivity()); - - if (activity != nullptr) - { - jmethodID finishMethod = env->GetMethodID (AndroidActivity, "finish", "()V"); - - if (finishMethod != nullptr) - env->CallVoidMethod (activity.get(), finishMethod); - } - } - } - - void handleKeyboardHiddenCallback() - { - Component::unfocusAllComponents(); - } - - void handleAppPausedCallback() {} - - void handleAppResumedCallback() - { - if (isKioskModeComponent()) - setNavBarsHidden (navBarsHidden); - } - - //============================================================================== - AccessibilityNativeHandle* getNativeHandleForViewId (jint virtualViewId) const - { - if (auto* handler = (virtualViewId == HOST_VIEW_ID - ? component.getAccessibilityHandler() - : AccessibilityNativeHandle::getAccessibilityHandlerForVirtualViewId (virtualViewId))) - { - return handler->getNativeImplementation(); - } - - return nullptr; - } - - jboolean populateAccessibilityNodeInfoCallback (jint virtualViewId, jobject info) const - { - if (auto* handle = getNativeHandleForViewId (virtualViewId)) - { - handle->populateNodeInfo (info); - return true; - } - - return false; - } - - jboolean handlePerformActionCallback (jint virtualViewId, jint action, jobject arguments) const - { - if (auto* handle = getNativeHandleForViewId (virtualViewId)) - return handle->performAction (action, arguments); - - return false; - } - - static jobject getFocusViewIdForHandler (const AccessibilityHandler* handler) - { - if (handler != nullptr) - return getEnv()->NewObject (JavaInteger, - JavaInteger.constructor, - handler->getNativeImplementation()->getVirtualViewId()); - - return nullptr; - } - - jobject getInputFocusViewIdCallback() - { - if (auto* comp = dynamic_cast (findCurrentTextInputTarget())) - return getFocusViewIdForHandler (comp->getAccessibilityHandler()); - - return nullptr; - } - - jobject getAccessibilityFocusViewIdCallback() const - { - if (auto* handler = component.getAccessibilityHandler()) - { - if (auto* modal = Component::getCurrentlyModalComponent()) - { - if (! component.isParentOf (modal) - && component.isCurrentlyBlockedByAnotherModalComponent()) - { - if (auto* modalHandler = modal->getAccessibilityHandler()) - { - if (auto* focusChild = modalHandler->getChildFocus()) - return getFocusViewIdForHandler (focusChild); - - return getFocusViewIdForHandler (modalHandler); - } - } - } - - if (auto* focusChild = handler->getChildFocus()) - return getFocusViewIdForHandler (focusChild); - } - - return nullptr; - } - - //============================================================================== - bool isFocused() const override - { - if (view != nullptr) - return view.callBooleanMethod (AndroidView.hasFocus); - - return false; - } - - void grabFocus() override - { - if (view != nullptr) - view.callBooleanMethod (AndroidView.requestFocus); - } - - void handleFocusChangeCallback (bool hasFocus) - { - if (isFullScreen()) - setFullScreen (true); - - if (hasFocus) - handleFocusGain(); - else - handleFocusLoss(); - } - - static const char* getVirtualKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept - { - switch (type) - { - case TextInputTarget::textKeyboard: return "text"; - case TextInputTarget::numericKeyboard: return "number"; - case TextInputTarget::decimalKeyboard: return "numberDecimal"; - case TextInputTarget::urlKeyboard: return "textUri"; - case TextInputTarget::emailAddressKeyboard: return "textEmailAddress"; - case TextInputTarget::phoneNumberKeyboard: return "phone"; - default: jassertfalse; break; - } - - return "text"; - } - - void textInputRequired (Point, TextInputTarget& target) override - { - view.callVoidMethod (ComponentPeerView.showKeyboard, - javaString (getVirtualKeyboardType (target.getKeyboardType())).get()); - } - - void dismissPendingTextInput() override - { - view.callVoidMethod (ComponentPeerView.showKeyboard, javaString ("").get()); - - if (! isTimerRunning()) - startTimer (500); - } - - //============================================================================== - void handlePaintCallback (jobject canvas, jobject paint) - { - auto* env = getEnv(); - - jobject rect = env->CallObjectMethod (canvas, AndroidCanvas.getClipBounds); - auto left = env->GetIntField (rect, AndroidRect.left); - auto top = env->GetIntField (rect, AndroidRect.top); - auto right = env->GetIntField (rect, AndroidRect.right); - auto bottom = env->GetIntField (rect, AndroidRect.bottom); - env->DeleteLocalRef (rect); - - auto clip = Rectangle::leftTopRightBottom (left, top, right, bottom); - - if (clip.isEmpty()) - return; - - auto sizeNeeded = clip.getWidth() * clip.getHeight(); - - if (sizeAllocated < sizeNeeded) - { - buffer.clear(); - sizeAllocated = sizeNeeded; - buffer = GlobalRef (LocalRef ((jobject) env->NewIntArray (sizeNeeded))); - } - - if (jint* dest = env->GetIntArrayElements ((jintArray) buffer.get(), nullptr)) - { - { - Image temp (new PreallocatedImage (clip.getWidth(), clip.getHeight(), - dest, ! component.isOpaque())); - - { - LowLevelGraphicsSoftwareRenderer g (temp); - g.setOrigin (-clip.getPosition()); - g.addTransform (AffineTransform::scale (scale)); - handlePaint (g); - } - } - - env->ReleaseIntArrayElements ((jintArray) buffer.get(), dest, 0); - - env->CallVoidMethod (canvas, AndroidCanvas.drawBitmap, (jintArray) buffer.get(), 0, clip.getWidth(), - (jfloat) clip.getX(), (jfloat) clip.getY(), - clip.getWidth(), clip.getHeight(), true, paint); - } - } - - void repaint (const Rectangle& userArea) override - { - auto area = (userArea.toFloat() * scale).toNearestInt(); - - GlobalRef localView (view); - - callOnMessageThread ([area, localView] - { - localView.callVoidMethod (AndroidView.invalidate, - area.getX(), area.getY(), area.getRight(), area.getBottom()); - }); - } - - void performAnyPendingRepaintsNow() override - { - // TODO - } - - void setAlpha (float /*newAlpha*/) override - { - // TODO - } - - StringArray getAvailableRenderingEngines() override - { - return StringArray ("Software Renderer"); - } - - //============================================================================== - static Point lastMousePos; - static int64 touchesDown; - - //============================================================================== - struct StartupActivityCallbackListener : public 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(); - - forceDisplayUpdate(); - } - } - }; - -private: - //============================================================================== - #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 (handleAccessibleHoverJni, "handleAccessibilityHover", "(JIFFJ)V") \ - CALLBACK (handleKeyDownJni, "handleKeyDown", "(JIII)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") \ - CALLBACK (populateAccessibilityNodeInfoJni, "populateAccessibilityNodeInfo", "(JILandroid/view/accessibility/AccessibilityNodeInfo;)Z") \ - CALLBACK (handlePerformActionJni, "handlePerformAction", "(JIILandroid/os/Bundle;)Z") \ - CALLBACK (getInputFocusViewIdJni, "getInputFocusViewId", "(J)Ljava/lang/Integer;") \ - CALLBACK (getAccessibilityFocusViewIdJni, "getAccessibilityFocusViewId", "(J)Ljava/lang/Integer;") \ - - DECLARE_JNI_CLASS_WITH_BYTECODE (ComponentPeerView, "com/rmsl/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 handleAccessibleHoverJni(JNIEnv*, jobject /*view*/, jlong host, jint c, jfloat x, jfloat y, jlong time) { if (auto* myself = reinterpret_cast (host)) myself->handleAccessibilityHoverCallback ((int) c, 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, jint kbFlags) { if (auto* myself = reinterpret_cast (host)) myself->handleKeyDownCallback ((int) k, (int) kc, (int) kbFlags); } - 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(); } - - static jboolean JNICALL populateAccessibilityNodeInfoJni (JNIEnv*, jobject /*view*/, jlong host, jint virtualViewId, jobject info) - { - if (auto* myself = reinterpret_cast (host)) - return myself->populateAccessibilityNodeInfoCallback (virtualViewId, info); - - return false; - } - - static jboolean JNICALL handlePerformActionJni (JNIEnv*, jobject /*view*/, jlong host, jint virtualViewId, jint action, jobject arguments) - { - if (auto* myself = reinterpret_cast (host)) - return myself->handlePerformActionCallback (virtualViewId, action, arguments); - - return false; - } - - static jobject JNICALL getInputFocusViewIdJni (JNIEnv*, jobject /*view*/, jlong host) - { - if (auto* myself = reinterpret_cast (host)) - return myself->getInputFocusViewIdCallback(); - - return nullptr; - } - - static jobject JNICALL getAccessibilityFocusViewIdJni (JNIEnv*, jobject /*view*/, jlong host) - { - if (auto* myself = reinterpret_cast (host)) - return myself->getAccessibilityFocusViewIdCallback(); - - return nullptr; - } - - //============================================================================== - struct ViewWindowInsetsListener : public juce::AndroidInterfaceImplementer - { - jobject onApplyWindowInsets (LocalRef v, LocalRef insets) - { - auto* env = getEnv(); - - LocalRef displayCutout (env->CallObjectMethod (insets.get(), AndroidWindowInsets.getDisplayCutout)); - - if (displayCutout != nullptr) - { - const auto& mainDisplay = *Desktop::getInstance().getDisplays().getPrimaryDisplay(); - auto newSafeAreaInsets = androidDisplayCutoutToBorderSize (displayCutout, mainDisplay.scale); - - if (newSafeAreaInsets != mainDisplay.safeAreaInsets) - forceDisplayUpdate(); - - auto* fieldId = env->GetStaticFieldID (AndroidWindowInsets, "CONSUMED", "Landroid/view/WindowInsets"); - jassert (fieldId != nullptr); - - return env->GetStaticObjectField (AndroidWindowInsets, fieldId); - } - - jmethodID onApplyWindowInsetsMethodId = env->GetMethodID (AndroidView, - "onApplyWindowInsets", - "(Landroid/view/WindowInsets;)Landroid/view/WindowInsets;"); - - jassert (onApplyWindowInsetsMethodId != nullptr); - - return env->CallObjectMethod (v.get(), onApplyWindowInsetsMethodId, insets.get()); - } - - 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 == "onApplyWindowInsets") - { - jassert (env->GetArrayLength (args) == 2); - - LocalRef windowView (env->GetObjectArrayElement (args, 0)); - LocalRef insets (env->GetObjectArrayElement (args, 1)); - - return onApplyWindowInsets (std::move (windowView), std::move (insets)); - } - - // invoke base class - return AndroidInterfaceImplementer::invoke (proxy, method, args); - } - }; - - //============================================================================== - struct PreallocatedImage : public ImagePixelData - { - PreallocatedImage (int width_, int height_, jint* data_, bool hasAlpha_) - : ImagePixelData (Image::ARGB, width_, height_), data (data_), hasAlpha (hasAlpha_) - { - if (hasAlpha_) - zeromem (data_, static_cast (width * height) * sizeof (jint)); - } - - ~PreallocatedImage() override - { - if (hasAlpha) - { - auto pix = (PixelARGB*) data; - - for (int i = width * height; --i >= 0;) - { - pix->unpremultiply(); - ++pix; - } - } - } - - std::unique_ptr createType() const override - { - return std::make_unique(); - } - - std::unique_ptr createLowLevelContext() override - { - return std::make_unique (Image (this)); - } - - void initialiseBitmapData (Image::BitmapData& bm, int x, int y, Image::BitmapData::ReadWriteMode /*mode*/) override - { - bm.lineStride = width * static_cast (sizeof (jint)); - bm.pixelStride = static_cast (sizeof (jint)); - bm.pixelFormat = Image::ARGB; - const auto offset = (size_t) x + (size_t) y * (size_t) width; - bm.data = (uint8*) (data + offset); - bm.size = sizeof (jint) * (((size_t) height * (size_t) width) - offset); - } - - ImagePixelData::Ptr clone() override - { - auto s = new PreallocatedImage (width, height, nullptr, hasAlpha); - s->allocatedData.malloc (sizeof (jint) * static_cast (width * height)); - s->data = s->allocatedData; - memcpy (s->data, data, sizeof (jint) * static_cast (width * height)); - return s; - } - - private: - jint* data; - HeapBlock allocatedData; - bool hasAlpha; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreallocatedImage) - }; - - //============================================================================== - void timerCallback() override - { - setNavBarsHidden (shouldNavBarsBeHidden (fullScreen)); - setFullScreen (fullScreen); - stopTimer(); - } - - bool isKioskModeComponent() const - { - if (auto* kiosk = Desktop::getInstance().getKioskModeComponent()) - return kiosk->getPeer() == this; - - return false; - } - - bool shouldNavBarsBeHidden (bool shouldBeFullScreen) const - { - return (shouldBeFullScreen && isKioskModeComponent()); - } - - void setNavBarsHidden (bool hidden) - { - if (navBarsHidden != hidden) - { - navBarsHidden = hidden; - - view.callVoidMethod (ComponentPeerView.setSystemUiVisibilityCompat, - (navBarsHidden ? (jint) (fullScreenFlags) : (jint) (SYSTEM_UI_FLAG_VISIBLE))); - } - } - - template - static void callOnMessageThread (Callback&& callback) - { - if (MessageManager::getInstance()->isThisTheMessageThread()) - callback(); - else - MessageManager::callAsync (std::forward (callback)); - } - - //============================================================================== - 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; - static constexpr int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS = 0x3; - - GlobalRef view, viewGroup, buffer; - bool viewGroupIsWindow = false, fullScreen = false, navBarsHidden = false; - int sizeAllocated = 0; - float scale = (float) Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidComponentPeer) -}; - -Point AndroidComponentPeer::lastMousePos; -int64 AndroidComponentPeer::touchesDown = 0; -AndroidComponentPeer* AndroidComponentPeer::frontWindow = nullptr; -GlobalRef AndroidComponentPeer::activityCallbackListener; -AndroidComponentPeer::ComponentPeerView_Class AndroidComponentPeer::ComponentPeerView; - -//============================================================================== -ComponentPeer* Component::createNewPeer (int styleFlags, void* nativeWindow) -{ - return new AndroidComponentPeer (*this, styleFlags, nativeWindow); -} - -//============================================================================== -bool Desktop::canUseSemiTransparentWindows() noexcept -{ - return true; -} - -class Desktop::NativeDarkModeChangeDetectorImpl : public ActivityLifecycleCallbacks -{ -public: - NativeDarkModeChangeDetectorImpl() - { - 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()); - } - } - - ~NativeDarkModeChangeDetectorImpl() override - { - LocalRef appContext (getAppContext()); - - if (appContext != nullptr && myself != nullptr) - { - auto* env = getEnv(); - - env->CallVoidMethod (appContext.get(), - AndroidApplication.unregisterActivityLifecycleCallbacks, - myself.get()); - clear(); - myself.clear(); - } - } - - bool isDarkModeEnabled() const noexcept { return darkModeEnabled; } - - void onActivityStarted (jobject /*activity*/) override - { - const auto isEnabled = getDarkModeSetting(); - - if (darkModeEnabled != isEnabled) - { - darkModeEnabled = isEnabled; - Desktop::getInstance().darkModeChanged(); - } - } - -private: - static bool getDarkModeSetting() - { - auto* env = getEnv(); - - const LocalRef resources (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources)); - const LocalRef configuration (env->CallObjectMethod (resources, AndroidResources.getConfiguration)); - - const auto uiMode = env->GetIntField (configuration, AndroidConfiguration.uiMode); - - return ((uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES); - } - - static constexpr int UI_MODE_NIGHT_MASK = 0x00000030, - UI_MODE_NIGHT_NO = 0x00000010, - UI_MODE_NIGHT_UNDEFINED = 0x00000000, - UI_MODE_NIGHT_YES = 0x00000020; - - GlobalRef myself; - bool darkModeEnabled = getDarkModeSetting(); - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl) -}; - -std::unique_ptr Desktop::createNativeDarkModeChangeDetectorImpl() -{ - return std::make_unique(); -} - -bool Desktop::isDarkModeActive() const -{ - return nativeDarkModeChangeDetectorImpl->isDarkModeEnabled(); -} - -double Desktop::getDefaultMasterScale() -{ - return 1.0; -} - -Desktop::DisplayOrientation Desktop::getCurrentOrientation() const -{ - enum - { - ROTATION_0 = 0, - ROTATION_90 = 1, - ROTATION_180 = 2, - ROTATION_270 = 3 - }; - - JNIEnv* env = getEnv(); - LocalRef windowServiceString (javaString ("window")); - - - LocalRef windowManager = LocalRef (env->CallObjectMethod (getAppContext().get(), AndroidContext.getSystemService, windowServiceString.get())); - - if (windowManager.get() != nullptr) - { - LocalRef display = LocalRef (env->CallObjectMethod (windowManager, AndroidWindowManager.getDefaultDisplay)); - - if (display.get() != nullptr) - { - int rotation = env->CallIntMethod (display, AndroidDisplay.getRotation); - - switch (rotation) - { - case ROTATION_0: return upright; - case ROTATION_90: return rotatedAntiClockwise; - case ROTATION_180: return upsideDown; - case ROTATION_270: return rotatedClockwise; - } - } - } - - jassertfalse; - return upright; -} - -bool MouseInputSource::SourceList::addSource() -{ - addSource (sources.size(), MouseInputSource::InputSourceType::touch); - return true; -} - -bool MouseInputSource::SourceList::canUseTouch() -{ - return true; -} - -Point MouseInputSource::getCurrentRawMousePosition() -{ - return AndroidComponentPeer::lastMousePos; -} - -void MouseInputSource::setRawMousePosition (Point) -{ - // not needed -} - -//============================================================================== -bool KeyPress::isKeyCurrentlyDown (int /*keyCode*/) -{ - // TODO - return false; -} - -JUCE_API void JUCE_CALLTYPE Process::hide() -{ - auto* env = getEnv(); - LocalRef currentActivity (getCurrentActivity().get()); - - 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()); - - env->CallVoidMethod (currentActivity.get(), AndroidContext.startActivity, intent.get()); - } -} - -//============================================================================== -// TODO -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") \ - METHOD (getWindow, "getWindow", "()Landroid/view/Window;") - -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 (std::shared_ptr callbackToUse, int resultToUse) - : callback (std::move (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::shared_ptr callback; - int result; -}; - -//============================================================================== -static void createAndroidDialog (const MessageBoxOptions& opts, - ModalComponentManager::Callback* callbackIn, - AlertWindowMappings::MapFn mapFn) -{ - auto* env = getEnv(); - - LocalRef builder (env->NewObject (AndroidAlertDialogBuilder, AndroidAlertDialogBuilder.construct, getMainActivity().get())); - - builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setTitle, javaString (opts.getTitle()).get())); - builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setMessage, javaString (opts.getMessage()).get())); - builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setCancelable, true)); - - std::shared_ptr sharedCallback (AlertWindowMappings::getWrappedCallback (callbackIn, mapFn)); - - builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setOnCancelListener, - CreateJavaInterface (new DialogListener (sharedCallback, 0), - "android/content/DialogInterface$OnCancelListener").get())); - - builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setPositiveButton, - javaString (opts.getButtonText (0)).get(), - CreateJavaInterface (new DialogListener (sharedCallback, 0), - "android/content/DialogInterface$OnClickListener").get())); - - if (opts.getButtonText (1).isNotEmpty()) - builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNegativeButton, - javaString (opts.getButtonText (1)).get(), - CreateJavaInterface (new DialogListener (sharedCallback, 1), - "android/content/DialogInterface$OnClickListener").get())); - - if (opts.getButtonText (2).isNotEmpty()) - builder = LocalRef (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.setNeutralButton, - javaString (opts.getButtonText (2)).get(), - CreateJavaInterface (new DialogListener (sharedCallback, 2), - "android/content/DialogInterface$OnClickListener").get())); - - LocalRef dialog (env->CallObjectMethod (builder.get(), AndroidAlertDialogBuilder.create)); - - LocalRef window (env->CallObjectMethod (dialog.get(), AndroidDialog.getWindow)); - - if (Desktop::getInstance().getKioskModeComponent() != nullptr) - { - env->CallVoidMethod (window.get(), AndroidWindow.setFlags, FLAG_NOT_FOCUSABLE, FLAG_NOT_FOCUSABLE); - LocalRef decorView (env->CallObjectMethod (window.get(), AndroidWindow.getDecorView)); - env->CallVoidMethod (decorView.get(), AndroidView.setSystemUiVisibility, fullScreenFlags); - } - - env->CallVoidMethod (dialog.get(), AndroidDialog.show); - - if (Desktop::getInstance().getKioskModeComponent() != nullptr) - env->CallVoidMethod (window.get(), AndroidWindow.clearFlags, FLAG_NOT_FOCUSABLE); -} - -void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - createAndroidDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("OK")), - callback, AlertWindowMappings::messageBox); -} - -bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - createAndroidDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("OK")) - .withButton (TRANS("Cancel")), - callback, AlertWindowMappings::okCancel); - - return false; -} - -int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - createAndroidDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("Yes")) - .withButton (TRANS("No")) - .withButton (TRANS("Cancel")), - callback, AlertWindowMappings::yesNoCancel); - - return 0; -} - -int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - createAndroidDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("Yes")) - .withButton (TRANS("No")), - callback, AlertWindowMappings::okCancel); - - return 0; -} - -void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options, - ModalComponentManager::Callback* callback) -{ - createAndroidDialog (options, callback, AlertWindowMappings::noMapping); -} - -void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options, - std::function callback) -{ - showAsync (options, ModalCallbackFunction::create (callback)); -} - -//============================================================================== -static bool androidScreenSaverEnabled = true; - -void Desktop::setScreenSaverEnabled (bool shouldEnable) -{ - constexpr auto FLAG_KEEP_SCREEN_ON = 0x80; - - if (shouldEnable != androidScreenSaverEnabled) - { - LocalRef activity (getMainActivity()); - - if (activity != nullptr) - { - auto* env = getEnv(); - - LocalRef mainWindow (env->CallObjectMethod (activity.get(), AndroidActivity.getWindow)); - env->CallVoidMethod (mainWindow.get(), AndroidWindow.setFlags, shouldEnable ? 0 : FLAG_KEEP_SCREEN_ON, FLAG_KEEP_SCREEN_ON); - } - - androidScreenSaverEnabled = shouldEnable; - } -} - -bool Desktop::isScreenSaverEnabled() -{ - return androidScreenSaverEnabled; -} - -//============================================================================== -void Desktop::setKioskComponent (Component* kioskComp, bool enableOrDisable, bool allowMenusAndBars) -{ - ignoreUnused (allowMenusAndBars); - - if (AndroidComponentPeer* peer = dynamic_cast (kioskComp->getPeer())) - peer->setFullScreen (enableOrDisable); - else - jassertfalse; // (this should have been checked by the caller) -} - -//============================================================================== -static jint getAndroidOrientationFlag (int orientations) noexcept -{ - enum - { - SCREEN_ORIENTATION_LANDSCAPE = 0, - SCREEN_ORIENTATION_PORTRAIT = 1, - SCREEN_ORIENTATION_USER = 2, - SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8, - SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9, - SCREEN_ORIENTATION_USER_LANDSCAPE = 11, - SCREEN_ORIENTATION_USER_PORTRAIT = 12, - }; - - switch (orientations) - { - case Desktop::upright: return (jint) SCREEN_ORIENTATION_PORTRAIT; - case Desktop::upsideDown: return (jint) SCREEN_ORIENTATION_REVERSE_PORTRAIT; - case Desktop::upright + Desktop::upsideDown: return (jint) SCREEN_ORIENTATION_USER_PORTRAIT; - case Desktop::rotatedAntiClockwise: return (jint) SCREEN_ORIENTATION_LANDSCAPE; - case Desktop::rotatedClockwise: return (jint) SCREEN_ORIENTATION_REVERSE_LANDSCAPE; - case Desktop::rotatedClockwise + Desktop::rotatedAntiClockwise: return (jint) SCREEN_ORIENTATION_USER_LANDSCAPE; - default: return (jint) SCREEN_ORIENTATION_USER; - } -} - -void Desktop::allowedOrientationsChanged() -{ - LocalRef activity (getMainActivity()); - - if (activity != nullptr) - getEnv()->CallVoidMethod (activity.get(), AndroidActivity.setRequestedOrientation, getAndroidOrientationFlag (allowedOrientations)); -} - -//============================================================================== -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; -}; - -//============================================================================== -struct MainActivityWindowLayoutListener : public LayoutChangeListener -{ - MainActivityWindowLayoutListener (std::function&& updateDisplaysCb) - : forceDisplayUpdate (std::move (updateDisplaysCb)) - { - } - - 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) - { - const auto& mainDisplay = *Desktop::getInstance().getDisplays().getPrimaryDisplay(); - auto userArea = (newBounds.toFloat() / mainDisplay.scale).toNearestInt(); - - if (userArea != mainDisplay.userArea) - forceDisplayUpdate(); - } - } - - std::function forceDisplayUpdate; -}; - -//============================================================================== -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 != nullptr) - 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.scale = env->GetFloatField (displayMetrics.get(), AndroidDisplayMetrics.density); - d.dpi = (d.scale * 160.f); - d.scale *= masterScale; - - d.totalArea = Rectangle (env->GetIntField (displayMetrics.get(), AndroidDisplayMetrics.widthPixels), - env->GetIntField (displayMetrics.get(), AndroidDisplayMetrics.heightPixels)) / d.scale; - - 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; - - if (supportsDisplayCutout()) - { - jmethodID getRootWindowInsetsMethodId = env->GetMethodID (AndroidView, - "getRootWindowInsets", - "()Landroid/view/WindowInsets;"); - - if (getRootWindowInsetsMethodId != nullptr) - { - LocalRef insets (env->CallObjectMethod (contentView.get(), getRootWindowInsetsMethodId)); - - if (insets != nullptr) - { - LocalRef displayCutout (env->CallObjectMethod (insets.get(), AndroidWindowInsets.getDisplayCutout)); - - if (displayCutout.get() != nullptr) - d.safeAreaInsets = androidDisplayCutoutToBorderSize (displayCutout, d.scale); - } - } - } - - static bool hasAddedMainActivityListener = false; - - if (! hasAddedMainActivityListener) - { - hasAddedMainActivityListener = true; - - env->CallVoidMethod (contentView.get(), AndroidView.addOnLayoutChangeListener, - CreateJavaInterface (new MainActivityWindowLayoutListener ([this] { refresh(); }), - "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); -} - -//============================================================================== -Image juce_createIconForFile (const File& /*file*/) -{ - return Image(); -} - -//============================================================================== -class MouseCursor::PlatformSpecificHandle -{ -public: - PlatformSpecificHandle (const MouseCursor::StandardCursorType) {} - PlatformSpecificHandle (const CustomMouseCursorInfo&) {} - - static void showInWindow (PlatformSpecificHandle*, ComponentPeer*) {} -}; - -//============================================================================== -bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& /*files*/, bool /*canMove*/, - Component* /*srcComp*/, std::function /*callback*/) -{ - jassertfalse; // no such thing on Android! - return false; -} - -bool DragAndDropContainer::performExternalDragDropOfText (const String& /*text*/, Component* /*srcComp*/, - std::function /*callback*/) -{ - jassertfalse; // no such thing on Android! - return false; -} - -//============================================================================== -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) -{ - 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() -{ - 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))); -} - -//============================================================================== -constexpr int extendedKeyModifier = 0x10000; - -const int KeyPress::spaceKey = ' '; -const int KeyPress::returnKey = extendedKeyModifier + 2; -const int KeyPress::escapeKey = extendedKeyModifier + 3; -const int KeyPress::backspaceKey = extendedKeyModifier + 4; -const int KeyPress::leftKey = extendedKeyModifier + 5; -const int KeyPress::rightKey = extendedKeyModifier + 6; -const int KeyPress::upKey = extendedKeyModifier + 7; -const int KeyPress::downKey = extendedKeyModifier + 8; -const int KeyPress::pageUpKey = extendedKeyModifier + 9; -const int KeyPress::pageDownKey = extendedKeyModifier + 10; -const int KeyPress::endKey = extendedKeyModifier + 11; -const int KeyPress::homeKey = extendedKeyModifier + 12; -const int KeyPress::deleteKey = extendedKeyModifier + 13; -const int KeyPress::insertKey = extendedKeyModifier + 14; -const int KeyPress::tabKey = extendedKeyModifier + 15; -const int KeyPress::F1Key = extendedKeyModifier + 16; -const int KeyPress::F2Key = extendedKeyModifier + 17; -const int KeyPress::F3Key = extendedKeyModifier + 18; -const int KeyPress::F4Key = extendedKeyModifier + 19; -const int KeyPress::F5Key = extendedKeyModifier + 20; -const int KeyPress::F6Key = extendedKeyModifier + 21; -const int KeyPress::F7Key = extendedKeyModifier + 22; -const int KeyPress::F8Key = extendedKeyModifier + 23; -const int KeyPress::F9Key = extendedKeyModifier + 24; -const int KeyPress::F10Key = extendedKeyModifier + 25; -const int KeyPress::F11Key = extendedKeyModifier + 26; -const int KeyPress::F12Key = extendedKeyModifier + 27; -const int KeyPress::F13Key = extendedKeyModifier + 28; -const int KeyPress::F14Key = extendedKeyModifier + 29; -const int KeyPress::F15Key = extendedKeyModifier + 30; -const int KeyPress::F16Key = extendedKeyModifier + 31; -const int KeyPress::F17Key = extendedKeyModifier + 32; -const int KeyPress::F18Key = extendedKeyModifier + 33; -const int KeyPress::F19Key = extendedKeyModifier + 34; -const int KeyPress::F20Key = extendedKeyModifier + 35; -const int KeyPress::F21Key = extendedKeyModifier + 36; -const int KeyPress::F22Key = extendedKeyModifier + 37; -const int KeyPress::F23Key = extendedKeyModifier + 38; -const int KeyPress::F24Key = extendedKeyModifier + 39; -const int KeyPress::F25Key = extendedKeyModifier + 40; -const int KeyPress::F26Key = extendedKeyModifier + 41; -const int KeyPress::F27Key = extendedKeyModifier + 42; -const int KeyPress::F28Key = extendedKeyModifier + 43; -const int KeyPress::F29Key = extendedKeyModifier + 44; -const int KeyPress::F30Key = extendedKeyModifier + 45; -const int KeyPress::F31Key = extendedKeyModifier + 46; -const int KeyPress::F32Key = extendedKeyModifier + 47; -const int KeyPress::F33Key = extendedKeyModifier + 48; -const int KeyPress::F34Key = extendedKeyModifier + 49; -const int KeyPress::F35Key = extendedKeyModifier + 50; -const int KeyPress::numberPad0 = extendedKeyModifier + 51; -const int KeyPress::numberPad1 = extendedKeyModifier + 52; -const int KeyPress::numberPad2 = extendedKeyModifier + 53; -const int KeyPress::numberPad3 = extendedKeyModifier + 54; -const int KeyPress::numberPad4 = extendedKeyModifier + 55; -const int KeyPress::numberPad5 = extendedKeyModifier + 56; -const int KeyPress::numberPad6 = extendedKeyModifier + 57; -const int KeyPress::numberPad7 = extendedKeyModifier + 58; -const int KeyPress::numberPad8 = extendedKeyModifier + 59; -const int KeyPress::numberPad9 = extendedKeyModifier + 60; -const int KeyPress::numberPadAdd = extendedKeyModifier + 61; -const int KeyPress::numberPadSubtract = extendedKeyModifier + 62; -const int KeyPress::numberPadMultiply = extendedKeyModifier + 63; -const int KeyPress::numberPadDivide = extendedKeyModifier + 64; -const int KeyPress::numberPadSeparator = extendedKeyModifier + 65; -const int KeyPress::numberPadDecimalPoint = extendedKeyModifier + 66; -const int KeyPress::numberPadEquals = extendedKeyModifier + 67; -const int KeyPress::numberPadDelete = extendedKeyModifier + 68; -const int KeyPress::playKey = extendedKeyModifier + 69; -const int KeyPress::stopKey = extendedKeyModifier + 70; -const int KeyPress::fastForwardKey = extendedKeyModifier + 71; -const int KeyPress::rewindKey = extendedKeyModifier + 72; - -//============================================================================== -#ifdef JUCE_PUSH_NOTIFICATIONS_ACTIVITY - struct JuceActivityNewIntentListener - { - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (appNewIntent, "appNewIntent", "(Landroid/content/Intent;)V") - - DECLARE_JNI_CLASS (JavaActivity, JUCE_PUSH_NOTIFICATIONS_ACTIVITY) - #undef JNI_CLASS_MEMBERS - - static void JNICALL appNewIntent (JNIEnv*, jobject /*activity*/, jobject intentData) - { - juce_handleNotificationIntent (static_cast (intentData)); - } - }; - - JuceActivityNewIntentListener::JavaActivity_Class JuceActivityNewIntentListener::JavaActivity; -#endif - -} // namespace juce diff --git a/source/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp b/source/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp deleted file mode 100644 index f15365b64..000000000 --- a/source/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -#if ! defined (__IPHONE_10_0) || __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_10_0 - using UIActivityType = NSString*; -#endif - -class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl, - private Component -{ -public: - ContentSharerNativeImpl (ContentSharer& cs) - : owner (cs) - { - static PopoverDelegateClass cls; - popoverDelegate.reset ([cls.createInstance() init]); - } - - ~ContentSharerNativeImpl() override - { - exitModalState (0); - } - - void shareFiles (const Array& files) override - { - auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) files.size()]; - - for (const auto& f : files) - { - NSString* nativeFilePath = nil; - - if (f.isLocalFile()) - { - nativeFilePath = juceStringToNS (f.getLocalFile().getFullPathName()); - } - else - { - auto filePath = f.toString (false); - - auto* fileDirectory = filePath.contains ("/") - ? juceStringToNS (filePath.upToLastOccurrenceOf ("/", false, false)) - : [NSString string]; - - auto fileName = juceStringToNS (filePath.fromLastOccurrenceOf ("/", false, false) - .upToLastOccurrenceOf (".", false, false)); - - auto fileExt = juceStringToNS (filePath.fromLastOccurrenceOf (".", false, false)); - - if ([fileDirectory length] == NSUInteger (0)) - nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName - ofType: fileExt]; - else - nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName - ofType: fileExt - inDirectory: fileDirectory]; - } - - if (nativeFilePath != nil) - [urls addObject: [NSURL fileURLWithPath: nativeFilePath]]; - } - - share (urls); - } - - void shareText (const String& text) override - { - auto array = [NSArray arrayWithObject: juceStringToNS (text)]; - share (array); - } - -private: - void share (NSArray* items) - { - if ([items count] == 0) - { - jassertfalse; - owner.sharingFinished (false, "No valid items found for sharing."); - return; - } - - controller.reset ([[UIActivityViewController alloc] initWithActivityItems: items - applicationActivities: nil]); - - controller.get().excludedActivityTypes = nil; - - controller.get().completionWithItemsHandler = ^ (UIActivityType type, BOOL completed, - NSArray* returnedItems, NSError* error) - { - ignoreUnused (type); - ignoreUnused (returnedItems); - - succeeded = completed; - - if (error != nil) - errorDescription = nsStringToJuce ([error localizedDescription]); - - exitModalState (0); - }; - - controller.get().modalTransitionStyle = UIModalTransitionStyleCoverVertical; - - auto bounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; - setBounds (bounds); - - setAlwaysOnTop (true); - setVisible (true); - addToDesktop (0); - - enterModalState (true, - ModalCallbackFunction::create ([this] (int) - { - owner.sharingFinished (succeeded, errorDescription); - }), - false); - } - - static bool isIPad() - { - return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad; - } - - //============================================================================== - void parentHierarchyChanged() override - { - auto* newPeer = dynamic_cast (getPeer()); - - if (peer != newPeer) - { - peer = newPeer; - - if (isIPad()) - { - controller.get().preferredContentSize = peer->view.frame.size; - - auto screenBounds = [UIScreen mainScreen].bounds; - - auto* popoverController = controller.get().popoverPresentationController; - popoverController.sourceView = peer->view; - popoverController.sourceRect = CGRectMake (0.f, screenBounds.size.height - 10.f, screenBounds.size.width, 10.f); - popoverController.canOverlapSourceViewRect = YES; - popoverController.delegate = popoverDelegate.get(); - } - - if (auto* parentController = peer->controller) - [parentController showViewController: controller.get() sender: parentController]; - } - } - - //============================================================================== - struct PopoverDelegateClass : public ObjCClass> - { - PopoverDelegateClass() : ObjCClass> ("PopoverDelegateClass_") - { - addMethod (@selector (popoverPresentationController:willRepositionPopoverToRect:inView:), willRepositionPopover); - - registerClass(); - } - - //============================================================================== - static void willRepositionPopover (id, SEL, UIPopoverPresentationController*, CGRect* rect, UIView*) - { - auto screenBounds = [UIScreen mainScreen].bounds; - - rect->origin.x = 0.f; - rect->origin.y = screenBounds.size.height - 10.f; - rect->size.width = screenBounds.size.width; - rect->size.height = 10.f; - } - }; - - ContentSharer& owner; - UIViewComponentPeer* peer = nullptr; - NSUniquePtr controller; - NSUniquePtr> popoverDelegate; - - bool succeeded = false; - String errorDescription; -}; - -//============================================================================== -ContentSharer::Pimpl* ContentSharer::createPimpl() -{ - return new ContentSharerNativeImpl (*this); -} - -} // namespace juce diff --git a/source/modules/juce_gui_basics/native/juce_ios_FileChooser.mm b/source/modules/juce_gui_basics/native/juce_ios_FileChooser.mm deleted file mode 100644 index 5da0b68ea..000000000 --- a/source/modules/juce_gui_basics/native/juce_ios_FileChooser.mm +++ /dev/null @@ -1,412 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -#if ! (defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0) - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - #define JUCE_DEPRECATION_IGNORED 1 -#endif - -class FileChooser::Native : public FileChooser::Pimpl, - public Component, - private AsyncUpdater -{ -public: - Native (FileChooser& fileChooser, int flags) - : owner (fileChooser) - { - static FileChooserDelegateClass delegateClass; - delegate.reset ([delegateClass.createInstance() init]); - FileChooserDelegateClass::setOwner (delegate.get(), this); - - static FileChooserControllerClass controllerClass; - auto* controllerClassInstance = controllerClass.createInstance(); - - String firstFileExtension; - auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension)); - - if ((flags & FileBrowserComponent::saveMode) != 0) - { - auto currentFileOrDirectory = owner.startingFile; - - UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile() - ? UIDocumentPickerModeExportToService - : UIDocumentPickerModeMoveToService; - - if (! currentFileOrDirectory.existsAsFile()) - { - auto filename = getFilename (currentFileOrDirectory, firstFileExtension); - auto tmpDirectory = File::createTempFile ("JUCE-filepath"); - - if (tmpDirectory.createDirectory().wasOk()) - { - currentFileOrDirectory = tmpDirectory.getChildFile (filename); - currentFileOrDirectory.replaceWithText (""); - } - else - { - // Temporary directory creation failed! You need to specify a - // path you have write access to. Saving will not work for - // current path. - jassertfalse; - } - } - - auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())]; - - controller.reset ([controllerClassInstance initWithURL: url - inMode: pickerMode]); - - [url release]; - } - else - { - controller.reset ([controllerClassInstance initWithDocumentTypes: utTypeArray - inMode: UIDocumentPickerModeOpen]); - if (@available (iOS 11.0, *)) - [controller.get() setAllowsMultipleSelection: (flags & FileBrowserComponent::canSelectMultipleItems) != 0]; - } - - FileChooserControllerClass::setOwner (controller.get(), this); - - [controller.get() setDelegate: delegate.get()]; - [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve]; - - setOpaque (false); - - if (fileChooser.parent != nullptr) - { - [controller.get() setModalPresentationStyle: UIModalPresentationFullScreen]; - - auto chooserBounds = fileChooser.parent->getBounds(); - setBounds (chooserBounds); - - setAlwaysOnTop (true); - fileChooser.parent->addAndMakeVisible (this); - } - else - { - if (SystemStats::isRunningInAppExtensionSandbox()) - { - // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a - // parent component (for example your editor) to parent the native file chooser window. To do this - // specify a parent component in the FileChooser's constructor! - jassertfalse; - return; - } - - auto chooserBounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; - setBounds (chooserBounds); - - setAlwaysOnTop (true); - setVisible (true); - addToDesktop (0); - } - } - - ~Native() override - { - exitModalState (0); - - // Our old peer may not have received a becomeFirstResponder call at this point, - // so the static currentlyFocusedPeer may be null. - // We'll try to find an appropriate peer to focus. - - for (auto i = 0; i < ComponentPeer::getNumPeers(); ++i) - if (auto* p = ComponentPeer::getPeer (i)) - if (p != getPeer()) - if (auto* view = (UIView*) p->getNativeHandle()) - [view becomeFirstResponder]; - } - - void launch() override - { - enterModalState (true, nullptr, true); - } - - void runModally() override - { - #if JUCE_MODAL_LOOPS_PERMITTED - runModalLoop(); - #else - jassertfalse; - #endif - } - - void parentHierarchyChanged() override - { - auto* newPeer = dynamic_cast (getPeer()); - - if (peer != newPeer) - { - peer = newPeer; - - if (peer != nullptr) - { - if (auto* parentController = peer->controller) - [parentController showViewController: controller.get() sender: parentController]; - - peer->toFront (false); - } - } - } - -private: - //============================================================================== - void handleAsyncUpdate() override - { - pickerWasCancelled(); - } - - //============================================================================== - static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension) - { - auto filters = StringArray::fromTokens (filterWildcards, ";", ""); - StringArray result; - - firstExtension = {}; - - if (! filters.contains ("*") && filters.size() > 0) - { - for (auto filter : filters) - { - if (filter.isEmpty()) - continue; - - // iOS only supports file extension wild cards - jassert (filter.upToLastOccurrenceOf (".", true, false) == "*."); - - auto fileExtension = filter.fromLastOccurrenceOf (".", false, false); - CFUniquePtr fileExtensionCF (fileExtension.toCFString()); - - if (firstExtension.isEmpty()) - firstExtension = fileExtension; - - if (auto tag = CFUniquePtr (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr))) - result.add (String::fromCFString (tag.get())); - } - } - else - { - result.add ("public.data"); - } - - return result; - } - - static String getFilename (const File& path, const String& fallbackExtension) - { - auto filename = path.getFileNameWithoutExtension(); - auto extension = path.getFileExtension().substring (1); - - if (filename.isEmpty()) - filename = "Untitled"; - - if (extension.isEmpty()) - extension = fallbackExtension; - - if (extension.isNotEmpty()) - filename += "." + extension; - - return filename; - } - - //============================================================================== - void didPickDocumentsAtURLs (NSArray* urls) - { - cancelPendingUpdate(); - - const auto isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService - || controller.get().documentPickerMode == UIDocumentPickerModeMoveToService; - const auto accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges; - - auto* fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease]; - auto* intents = [[[NSMutableArray alloc] init] autorelease]; - - for (NSURL* url in urls) - { - auto* fileAccessIntent = isWriting - ? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions] - : [NSFileAccessIntent readingIntentWithURL: url options: accessOptions]; - [intents addObject: fileAccessIntent]; - } - - [fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err) - { - if (err != nil) - { - auto desc = [err localizedDescription]; - ignoreUnused (desc); - jassertfalse; - return; - } - - Array result; - - for (NSURL* url in urls) - { - [url startAccessingSecurityScopedResource]; - - NSError* error = nil; - - auto* bookmark = [url bookmarkDataWithOptions: 0 - includingResourceValuesForKeys: nil - relativeToURL: nil - error: &error]; - - [bookmark retain]; - - [url stopAccessingSecurityScopedResource]; - - URL juceUrl (nsStringToJuce ([url absoluteString])); - - if (error == nil) - { - setURLBookmark (juceUrl, (void*) bookmark); - } - else - { - auto desc = [error localizedDescription]; - ignoreUnused (desc); - jassertfalse; - } - - result.add (std::move (juceUrl)); - } - - owner.finished (std::move (result)); - }]; - } - - void didPickDocumentAtURL (NSURL* url) - { - didPickDocumentsAtURLs (@[url]); - } - - void pickerWasCancelled() - { - cancelPendingUpdate(); - owner.finished ({}); - // Calling owner.finished will delete this Pimpl instance, so don't call any more member functions here! - } - - //============================================================================== - struct FileChooserDelegateClass : public ObjCClass> - { - FileChooserDelegateClass() : ObjCClass> ("FileChooserDelegate_") - { - addIvar ("owner"); - - addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL); - addMethod (@selector (documentPicker:didPickDocumentsAtURLs:), didPickDocumentsAtURLs); - addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled); - - addProtocol (@protocol (UIDocumentPickerDelegate)); - - registerClass(); - } - - static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); } - static Native* getOwner (id self) { return getIvar (self, "owner"); } - - //============================================================================== - static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url) - { - if (auto* picker = getOwner (self)) - picker->didPickDocumentAtURL (url); - } - - static void didPickDocumentsAtURLs (id self, SEL, UIDocumentPickerViewController*, NSArray* urls) - { - if (auto* picker = getOwner (self)) - picker->didPickDocumentsAtURLs (urls); - } - - static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*) - { - if (auto* picker = getOwner (self)) - picker->pickerWasCancelled(); - } - }; - - struct FileChooserControllerClass : public ObjCClass - { - FileChooserControllerClass() : ObjCClass ("FileChooserController_") - { - addIvar ("owner"); - addMethod (@selector (viewDidDisappear:), viewDidDisappear); - - registerClass(); - } - - static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); } - static Native* getOwner (id self) { return getIvar (self, "owner"); } - - //============================================================================== - static void viewDidDisappear (id self, SEL, BOOL animated) - { - sendSuperclassMessage (self, @selector (viewDidDisappear:), animated); - - if (auto* picker = getOwner (self)) - picker->triggerAsyncUpdate(); - } - }; - - //============================================================================== - FileChooser& owner; - NSUniquePtr> delegate; - NSUniquePtr controller; - UIViewComponentPeer* peer = nullptr; - - static FileChooserDelegateClass fileChooserDelegateClass; - static FileChooserControllerClass fileChooserControllerClass; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) -}; - -//============================================================================== -bool FileChooser::isPlatformDialogAvailable() -{ - #if JUCE_DISABLE_NATIVE_FILECHOOSERS - return false; - #else - return true; - #endif -} - -std::shared_ptr FileChooser::showPlatformDialog (FileChooser& owner, int flags, - FilePreviewComponent*) -{ - return std::make_shared (owner, flags); -} - -#if JUCE_DEPRECATION_IGNORED - JUCE_END_IGNORE_WARNINGS_GCC_LIKE -#endif - -} // namespace juce diff --git a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm deleted file mode 100644 index 3f293b5ad..000000000 --- a/source/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm +++ /dev/null @@ -1,1400 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include "juce_mac_CGMetalLayerRenderer.h" - -#if TARGET_OS_SIMULATOR && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS - #warning JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS uses parts of the Metal API that are currently unsupported in the simulator - falling back to JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS=0 - #undef JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS -#endif - -#if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 - #define JUCE_HAS_IOS_POINTER_SUPPORT 1 -#else - #define JUCE_HAS_IOS_POINTER_SUPPORT 0 -#endif - -namespace juce -{ - -class UIViewComponentPeer; - -static UIInterfaceOrientation getWindowOrientation() -{ - UIApplication* sharedApplication = [UIApplication sharedApplication]; - - #if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 - if (@available (iOS 13.0, *)) - { - for (UIScene* scene in [sharedApplication connectedScenes]) - if ([scene isKindOfClass: [UIWindowScene class]]) - return [(UIWindowScene*) scene interfaceOrientation]; - } - #endif - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - return [sharedApplication statusBarOrientation]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE -} - -namespace Orientations -{ - static Desktop::DisplayOrientation convertToJuce (UIInterfaceOrientation orientation) - { - switch (orientation) - { - case UIInterfaceOrientationPortrait: return Desktop::upright; - case UIInterfaceOrientationPortraitUpsideDown: return Desktop::upsideDown; - case UIInterfaceOrientationLandscapeLeft: return Desktop::rotatedClockwise; - case UIInterfaceOrientationLandscapeRight: return Desktop::rotatedAntiClockwise; - case UIInterfaceOrientationUnknown: - default: jassertfalse; // unknown orientation! - } - - return Desktop::upright; - } - - static UIInterfaceOrientation convertFromJuce (Desktop::DisplayOrientation orientation) - { - switch (orientation) - { - case Desktop::upright: return UIInterfaceOrientationPortrait; - case Desktop::upsideDown: return UIInterfaceOrientationPortraitUpsideDown; - case Desktop::rotatedClockwise: return UIInterfaceOrientationLandscapeLeft; - case Desktop::rotatedAntiClockwise: return UIInterfaceOrientationLandscapeRight; - case Desktop::allOrientations: - default: jassertfalse; // unknown orientation! - } - - return UIInterfaceOrientationPortrait; - } - - - static NSUInteger getSupportedOrientations() - { - NSUInteger allowed = 0; - auto& d = Desktop::getInstance(); - - if (d.isOrientationEnabled (Desktop::upright)) allowed |= UIInterfaceOrientationMaskPortrait; - if (d.isOrientationEnabled (Desktop::upsideDown)) allowed |= UIInterfaceOrientationMaskPortraitUpsideDown; - if (d.isOrientationEnabled (Desktop::rotatedClockwise)) allowed |= UIInterfaceOrientationMaskLandscapeLeft; - if (d.isOrientationEnabled (Desktop::rotatedAntiClockwise)) allowed |= UIInterfaceOrientationMaskLandscapeRight; - - return allowed; - } -} - -enum class MouseEventFlags -{ - none, - down, - up, - upAndCancel, -}; - -//============================================================================== -} // namespace juce - -using namespace juce; - -struct CADisplayLinkDeleter -{ - void operator() (CADisplayLink* displayLink) const noexcept - { - [displayLink invalidate]; - [displayLink release]; - } -}; - -@interface JuceUIView : UIView -{ -@public - UIViewComponentPeer* owner; - UITextView* hiddenTextView; - std::unique_ptr displayLink; -} - -- (JuceUIView*) initWithOwner: (UIViewComponentPeer*) owner withFrame: (CGRect) frame; -- (void) dealloc; - -+ (Class) layerClass; - -- (void) displayLinkCallback: (CADisplayLink*) dl; - -- (void) drawRect: (CGRect) r; - -- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event; -- (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event; -- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event; -- (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event; - -#if JUCE_HAS_IOS_POINTER_SUPPORT -- (void) onHover: (UIHoverGestureRecognizer*) gesture API_AVAILABLE (ios (13.0)); -- (void) onScroll: (UIPanGestureRecognizer*) gesture; -#endif - -- (BOOL) becomeFirstResponder; -- (BOOL) resignFirstResponder; -- (BOOL) canBecomeFirstResponder; - -- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text; - -- (void) traitCollectionDidChange: (UITraitCollection*) previousTraitCollection; - -- (BOOL) isAccessibilityElement; -- (CGRect) accessibilityFrame; -- (NSArray*) accessibilityElements; -@end - -//============================================================================== -@interface JuceUIViewController : UIViewController -{ -} - -- (JuceUIViewController*) init; - -- (NSUInteger) supportedInterfaceOrientations; -- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation; -- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration; -- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation; -- (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id) coordinator; -- (BOOL) prefersStatusBarHidden; -- (UIStatusBarStyle) preferredStatusBarStyle; - -- (void) viewDidLoad; -- (void) viewWillAppear: (BOOL) animated; -- (void) viewDidAppear: (BOOL) animated; -- (void) viewWillLayoutSubviews; -- (void) viewDidLayoutSubviews; -@end - -//============================================================================== -@interface JuceUIWindow : UIWindow -{ -@private - UIViewComponentPeer* owner; -} - -- (void) setOwner: (UIViewComponentPeer*) owner; -- (void) becomeKeyWindow; -@end - -//============================================================================== -//============================================================================== -namespace juce -{ - -struct UIViewPeerControllerReceiver -{ - virtual ~UIViewPeerControllerReceiver() = default; - virtual void setViewController (UIViewController*) = 0; -}; - -class UIViewComponentPeer : public ComponentPeer, - private FocusChangeListener, - private UIViewPeerControllerReceiver -{ -public: - UIViewComponentPeer (Component&, int windowStyleFlags, UIView* viewToAttachTo); - ~UIViewComponentPeer() override; - - //============================================================================== - void* getNativeHandle() const override { return view; } - void setVisible (bool shouldBeVisible) override; - void setTitle (const String& title) override; - void setBounds (const Rectangle&, bool isNowFullScreen) override; - - void setViewController (UIViewController* newController) override - { - jassert (controller == nullptr); - controller = [newController retain]; - } - - Rectangle getBounds() const override { return getBounds (! isSharedWindow); } - Rectangle getBounds (bool global) const; - Point localToGlobal (Point relativePosition) override; - Point globalToLocal (Point screenPosition) override; - using ComponentPeer::localToGlobal; - using ComponentPeer::globalToLocal; - void setAlpha (float newAlpha) override; - void setMinimised (bool) override {} - bool isMinimised() const override { return false; } - void setFullScreen (bool shouldBeFullScreen) override; - bool isFullScreen() const override { return fullScreen; } - bool contains (Point localPos, bool trueIfInAChildWindow) const override; - OptionalBorderSize getFrameSizeIfPresent() const override { return {}; } - BorderSize getFrameSize() const override { return BorderSize(); } - bool setAlwaysOnTop (bool alwaysOnTop) override; - void toFront (bool makeActiveWindow) override; - void toBehind (ComponentPeer* other) override; - void setIcon (const Image& newIcon) override; - StringArray getAvailableRenderingEngines() override { return StringArray ("CoreGraphics Renderer"); } - - void displayLinkCallback(); - - void drawRect (CGRect); - void drawRectWithContext (CGContextRef, CGRect); - bool canBecomeKeyWindow(); - - //============================================================================== - void viewFocusGain(); - void viewFocusLoss(); - bool isFocused() const override; - void grabFocus() override; - void textInputRequired (Point, TextInputTarget&) override; - - BOOL textViewReplaceCharacters (Range, const String&); - void updateHiddenTextContent (TextInputTarget*); - void globalFocusChanged (Component*) override; - - void updateScreenBounds(); - - void handleTouches (UIEvent*, MouseEventFlags); - - #if JUCE_HAS_IOS_POINTER_SUPPORT - API_AVAILABLE (ios (13.0)) void onHover (UIHoverGestureRecognizer*); - void onScroll (UIPanGestureRecognizer*); - #endif - - //============================================================================== - void repaint (const Rectangle& area) override; - void performAnyPendingRepaintsNow() override; - - //============================================================================== - UIWindow* window = nil; - JuceUIView* view = nil; - UIViewController* controller = nil; - const bool isSharedWindow, isAppex; - bool fullScreen = false, insideDrawRect = false; - - static int64 getMouseTime (NSTimeInterval timestamp) noexcept - { - return (Time::currentTimeMillis() - Time::getMillisecondCounter()) - + (int64) (timestamp * 1000.0); - } - - static int64 getMouseTime (UIEvent* e) noexcept - { - return getMouseTime ([e timestamp]); - } - - static NSString* getDarkModeNotificationName() - { - return @"ViewDarkModeChanged"; - } - - static MultiTouchMapper currentTouches; - -private: - void appStyleChanged() override - { - [controller setNeedsStatusBarAppearanceUpdate]; - } - - //============================================================================== - class AsyncRepaintMessage : public CallbackMessage - { - public: - UIViewComponentPeer* const peer; - const Rectangle rect; - - AsyncRepaintMessage (UIViewComponentPeer* const p, const Rectangle& r) - : peer (p), rect (r) - { - } - - void messageCallback() override - { - if (ComponentPeer::isValidPeer (peer)) - peer->repaint (rect); - } - }; - - std::unique_ptr metalRenderer; - RectangleList deferredRepaints; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) -}; - -static UIViewComponentPeer* getViewPeer (JuceUIViewController* c) -{ - if (JuceUIView* juceView = (JuceUIView*) [c view]) - return juceView->owner; - - jassertfalse; - return nullptr; -} - -static void sendScreenBoundsUpdate (JuceUIViewController* c) -{ - if (auto* peer = getViewPeer (c)) - peer->updateScreenBounds(); -} - -static bool isKioskModeView (JuceUIViewController* c) -{ - if (auto* peer = getViewPeer (c)) - return Desktop::getInstance().getKioskModeComponent() == &(peer->getComponent()); - - return false; -} - -MultiTouchMapper UIViewComponentPeer::currentTouches; - -} // namespace juce - -//============================================================================== -//============================================================================== -@implementation JuceUIViewController - -- (JuceUIViewController*) init -{ - self = [super init]; - - return self; -} - -- (NSUInteger) supportedInterfaceOrientations -{ - return Orientations::getSupportedOrientations(); -} - -- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation -{ - return Desktop::getInstance().isOrientationEnabled (Orientations::convertToJuce (interfaceOrientation)); -} - -- (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation - duration: (NSTimeInterval) duration -{ - ignoreUnused (toInterfaceOrientation, duration); - - [UIView setAnimationsEnabled: NO]; // disable this because it goes the wrong way and looks like crap. -} - -- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation -{ - ignoreUnused (fromInterfaceOrientation); - sendScreenBoundsUpdate (self); - [UIView setAnimationsEnabled: YES]; -} - -- (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id) coordinator -{ - [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator]; - [coordinator animateAlongsideTransition: nil completion: ^void (id) - { - sendScreenBoundsUpdate (self); - }]; -} - -- (BOOL) prefersStatusBarHidden -{ - if (isKioskModeView (self)) - return true; - - return [[[NSBundle mainBundle] objectForInfoDictionaryKey: @"UIStatusBarHidden"] boolValue]; -} - -#if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - - (BOOL) prefersHomeIndicatorAutoHidden - { - return isKioskModeView (self); - } -#endif - -- (UIStatusBarStyle) preferredStatusBarStyle -{ - #if defined (__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 - if (@available (iOS 13.0, *)) - { - if (auto* peer = getViewPeer (self)) - { - switch (peer->getAppStyle()) - { - case ComponentPeer::Style::automatic: - return UIStatusBarStyleDefault; - case ComponentPeer::Style::light: - return UIStatusBarStyleDarkContent; - case ComponentPeer::Style::dark: - return UIStatusBarStyleLightContent; - } - } - } - #endif - - return UIStatusBarStyleDefault; -} - -- (void) viewDidLoad -{ - sendScreenBoundsUpdate (self); - [super viewDidLoad]; -} - -- (void) viewWillAppear: (BOOL) animated -{ - sendScreenBoundsUpdate (self); - [super viewWillAppear:animated]; -} - -- (void) viewDidAppear: (BOOL) animated -{ - sendScreenBoundsUpdate (self); - [super viewDidAppear:animated]; -} - -- (void) viewWillLayoutSubviews -{ - sendScreenBoundsUpdate (self); -} - -- (void) viewDidLayoutSubviews -{ - sendScreenBoundsUpdate (self); -} - -@end - -@implementation JuceUIView - -- (JuceUIView*) initWithOwner: (UIViewComponentPeer*) peer - withFrame: (CGRect) frame -{ - [super initWithFrame: frame]; - owner = peer; - - displayLink.reset ([CADisplayLink displayLinkWithTarget: self - selector: @selector (displayLinkCallback:)]); - [displayLink.get() addToRunLoop: [NSRunLoop mainRunLoop] - forMode: NSDefaultRunLoopMode]; - - hiddenTextView = [[UITextView alloc] initWithFrame: CGRectZero]; - [self addSubview: hiddenTextView]; - hiddenTextView.delegate = self; - - hiddenTextView.autocapitalizationType = UITextAutocapitalizationTypeNone; - hiddenTextView.autocorrectionType = UITextAutocorrectionTypeNo; - hiddenTextView.inputAssistantItem.leadingBarButtonGroups = @[]; - hiddenTextView.inputAssistantItem.trailingBarButtonGroups = @[]; - - #if JUCE_HAS_IOS_POINTER_SUPPORT - if (@available (iOS 13.4, *)) - { - auto hoverRecognizer = [[[UIHoverGestureRecognizer alloc] initWithTarget: self action: @selector (onHover:)] autorelease]; - [hoverRecognizer setCancelsTouchesInView: NO]; - [hoverRecognizer setRequiresExclusiveTouchType: YES]; - [self addGestureRecognizer: hoverRecognizer]; - - auto panRecognizer = [[[UIPanGestureRecognizer alloc] initWithTarget: self action: @selector (onScroll:)] autorelease]; - [panRecognizer setCancelsTouchesInView: NO]; - [panRecognizer setRequiresExclusiveTouchType: YES]; - [panRecognizer setAllowedScrollTypesMask: UIScrollTypeMaskAll]; - [panRecognizer setMaximumNumberOfTouches: 0]; - [self addGestureRecognizer: panRecognizer]; - } - #endif - - return self; -} - -- (void) dealloc -{ - [hiddenTextView removeFromSuperview]; - [hiddenTextView release]; - - displayLink = nullptr; - - [super dealloc]; -} - -//============================================================================== -+ (Class) layerClass -{ - #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS - if (@available (iOS 12, *)) - return [CAMetalLayer class]; - #endif - - return [CALayer class]; -} - -- (void) displayLinkCallback: (CADisplayLink*) dl -{ - if (owner != nullptr) - owner->displayLinkCallback(); -} - -//============================================================================== -- (void) drawRect: (CGRect) r -{ - if (owner != nullptr) - owner->drawRect (r); -} - -//============================================================================== -- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event -{ - ignoreUnused (touches); - - if (owner != nullptr) - owner->handleTouches (event, MouseEventFlags::down); -} - -- (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event -{ - ignoreUnused (touches); - - if (owner != nullptr) - owner->handleTouches (event, MouseEventFlags::none); -} - -- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event -{ - ignoreUnused (touches); - - if (owner != nullptr) - owner->handleTouches (event, MouseEventFlags::up); -} - -- (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event -{ - if (owner != nullptr) - owner->handleTouches (event, MouseEventFlags::upAndCancel); - - [self touchesEnded: touches withEvent: event]; -} - -#if JUCE_HAS_IOS_POINTER_SUPPORT -- (void) onHover: (UIHoverGestureRecognizer*) gesture -{ - if (owner != nullptr) - owner->onHover (gesture); -} - -- (void) onScroll: (UIPanGestureRecognizer*) gesture -{ - if (owner != nullptr) - owner->onScroll (gesture); -} -#endif - -//============================================================================== -- (BOOL) becomeFirstResponder -{ - if (owner != nullptr) - owner->viewFocusGain(); - - return true; -} - -- (BOOL) resignFirstResponder -{ - if (owner != nullptr) - owner->viewFocusLoss(); - - return [super resignFirstResponder]; -} - -- (BOOL) canBecomeFirstResponder -{ - return owner != nullptr && owner->canBecomeKeyWindow(); -} - -- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text -{ - ignoreUnused (textView); - return owner->textViewReplaceCharacters (Range ((int) range.location, (int) (range.location + range.length)), - nsStringToJuce (text)); -} - -- (void) traitCollectionDidChange: (UITraitCollection*) previousTraitCollection -{ - [super traitCollectionDidChange: previousTraitCollection]; - - #if defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0 - if (@available (iOS 12.0, *)) - { - const auto wasDarkModeActive = ([previousTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark); - - if (wasDarkModeActive != Desktop::getInstance().isDarkModeActive()) - [[NSNotificationCenter defaultCenter] postNotificationName: UIViewComponentPeer::getDarkModeNotificationName() - object: nil]; - } - #endif -} - -- (BOOL) isAccessibilityElement -{ - return NO; -} - -- (CGRect) accessibilityFrame -{ - if (owner != nullptr) - if (auto* handler = owner->getComponent().getAccessibilityHandler()) - return convertToCGRect (handler->getComponent().getScreenBounds()); - - return CGRectZero; -} - -- (NSArray*) accessibilityElements -{ - if (owner != nullptr) - if (auto* handler = owner->getComponent().getAccessibilityHandler()) - return getContainerAccessibilityElements (*handler); - - return nil; -} - -@end - -//============================================================================== -@implementation JuceUIWindow - -- (void) setOwner: (UIViewComponentPeer*) peer -{ - owner = peer; -} - -- (void) becomeKeyWindow -{ - [super becomeKeyWindow]; - - if (owner != nullptr) - owner->grabFocus(); -} - -@end - -//============================================================================== -//============================================================================== -namespace juce -{ - -bool KeyPress::isKeyCurrentlyDown (int) -{ - return false; -} - -Point juce_lastMousePos; - -//============================================================================== -UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, UIView* viewToAttachTo) - : ComponentPeer (comp, windowStyleFlags), - isSharedWindow (viewToAttachTo != nil), - isAppex (SystemStats::isRunningInAppExtensionSandbox()) -{ - CGRect r = convertToCGRect (component.getBounds()); - - view = [[JuceUIView alloc] initWithOwner: this withFrame: r]; - - view.multipleTouchEnabled = YES; - view.hidden = true; - view.opaque = component.isOpaque(); - view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; - - #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS - if (@available (iOS 12, *)) - metalRenderer = std::make_unique ((CAMetalLayer*) view.layer, comp); - #endif - - if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0) - [[view layer] setDrawsAsynchronously: YES]; - - if (isSharedWindow) - { - window = [viewToAttachTo window]; - [viewToAttachTo addSubview: view]; - } - else - { - r = convertToCGRect (component.getBounds()); - r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height); - - window = [[JuceUIWindow alloc] initWithFrame: r]; - [((JuceUIWindow*) window) setOwner: this]; - - controller = [[JuceUIViewController alloc] init]; - controller.view = view; - window.rootViewController = controller; - - window.hidden = true; - window.opaque = component.isOpaque(); - window.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; - - if (component.isAlwaysOnTop()) - window.windowLevel = UIWindowLevelAlert; - - view.frame = CGRectMake (0, 0, r.size.width, r.size.height); - } - - setTitle (component.getName()); - setVisible (component.isVisible()); - - Desktop::getInstance().addFocusChangeListener (this); -} - -static UIViewComponentPeer* currentlyFocusedPeer = nullptr; - -UIViewComponentPeer::~UIViewComponentPeer() -{ - if (currentlyFocusedPeer == this) - currentlyFocusedPeer = nullptr; - - currentTouches.deleteAllTouchesForPeer (this); - Desktop::getInstance().removeFocusChangeListener (this); - - view->owner = nullptr; - [view removeFromSuperview]; - [view release]; - [controller release]; - - if (! isSharedWindow) - { - [((JuceUIWindow*) window) setOwner: nil]; - - #if defined (__IPHONE_13_0) - if (@available (iOS 13.0, *)) - window.windowScene = nil; - #endif - - [window release]; - } -} - -//============================================================================== -void UIViewComponentPeer::setVisible (bool shouldBeVisible) -{ - if (! isSharedWindow) - window.hidden = ! shouldBeVisible; - - view.hidden = ! shouldBeVisible; -} - -void UIViewComponentPeer::setTitle (const String&) -{ - // xxx is this possible? -} - -void UIViewComponentPeer::setBounds (const Rectangle& newBounds, const bool isNowFullScreen) -{ - fullScreen = isNowFullScreen; - - if (isSharedWindow) - { - CGRect r = convertToCGRect (newBounds); - - if (view.frame.size.width != r.size.width || view.frame.size.height != r.size.height) - [view setNeedsDisplay]; - - view.frame = r; - } - else - { - window.frame = convertToCGRect (newBounds); - view.frame = CGRectMake (0, 0, (CGFloat) newBounds.getWidth(), (CGFloat) newBounds.getHeight()); - - handleMovedOrResized(); - } -} - -Rectangle UIViewComponentPeer::getBounds (const bool global) const -{ - auto r = view.frame; - - if (global) - { - if (view.window != nil) - { - r = [view convertRect: r toView: view.window]; - r = [view.window convertRect: r toWindow: nil]; - } - else if (window != nil) - { - r.origin.x += window.frame.origin.x; - r.origin.y += window.frame.origin.y; - } - } - - return convertToRectInt (r); -} - -Point UIViewComponentPeer::localToGlobal (Point relativePosition) -{ - return relativePosition + getBounds (true).getPosition().toFloat(); -} - -Point UIViewComponentPeer::globalToLocal (Point screenPosition) -{ - return screenPosition - getBounds (true).getPosition().toFloat(); -} - -void UIViewComponentPeer::setAlpha (float newAlpha) -{ - [view.window setAlpha: (CGFloat) newAlpha]; -} - -void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen) -{ - if (! isSharedWindow) - { - auto r = shouldBeFullScreen ? Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea - : lastNonFullscreenBounds; - - if ((! shouldBeFullScreen) && r.isEmpty()) - r = getBounds(); - - // (can't call the component's setBounds method because that'll reset our fullscreen flag) - if (! r.isEmpty()) - setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen); - - component.repaint(); - } -} - -void UIViewComponentPeer::updateScreenBounds() -{ - auto& desktop = Desktop::getInstance(); - - auto oldArea = component.getBounds(); - auto oldDesktop = desktop.getDisplays().getPrimaryDisplay()->userArea; - - forceDisplayUpdate(); - - if (fullScreen) - { - fullScreen = false; - setFullScreen (true); - } - else if (! isSharedWindow) - { - auto newDesktop = desktop.getDisplays().getPrimaryDisplay()->userArea; - - if (newDesktop != oldDesktop) - { - // this will re-centre the window, but leave its size unchanged - - auto centreRelX = oldArea.getCentreX() / (float) oldDesktop.getWidth(); - auto centreRelY = oldArea.getCentreY() / (float) oldDesktop.getHeight(); - - auto x = ((int) (newDesktop.getWidth() * centreRelX)) - (oldArea.getWidth() / 2); - auto y = ((int) (newDesktop.getHeight() * centreRelY)) - (oldArea.getHeight() / 2); - - component.setBounds (oldArea.withPosition (x, y)); - } - } - - [view setNeedsDisplay]; -} - -bool UIViewComponentPeer::contains (Point localPos, bool trueIfInAChildWindow) const -{ - if (! ScalingHelpers::scaledScreenPosToUnscaled (component, component.getLocalBounds()).contains (localPos)) - return false; - - UIView* v = [view hitTest: convertToCGPoint (localPos) - withEvent: nil]; - - if (trueIfInAChildWindow) - return v != nil; - - return v == view; -} - -bool UIViewComponentPeer::setAlwaysOnTop (bool alwaysOnTop) -{ - if (! isSharedWindow) - window.windowLevel = alwaysOnTop ? UIWindowLevelAlert : UIWindowLevelNormal; - - return true; -} - -void UIViewComponentPeer::toFront (bool makeActiveWindow) -{ - if (isSharedWindow) - [[view superview] bringSubviewToFront: view]; - - if (makeActiveWindow && window != nil && component.isVisible()) - [window makeKeyAndVisible]; -} - -void UIViewComponentPeer::toBehind (ComponentPeer* other) -{ - if (auto* otherPeer = dynamic_cast (other)) - { - if (isSharedWindow) - [[view superview] insertSubview: view belowSubview: otherPeer->view]; - } - else - { - jassertfalse; // wrong type of window? - } -} - -void UIViewComponentPeer::setIcon (const Image& /*newIcon*/) -{ - // to do.. -} - -//============================================================================== -static float getMaximumTouchForce (UITouch* touch) noexcept -{ - if ([touch respondsToSelector: @selector (maximumPossibleForce)]) - return (float) touch.maximumPossibleForce; - - return 0.0f; -} - -static float getTouchForce (UITouch* touch) noexcept -{ - if ([touch respondsToSelector: @selector (force)]) - return (float) touch.force; - - return 0.0f; -} - -void UIViewComponentPeer::handleTouches (UIEvent* event, MouseEventFlags mouseEventFlags) -{ - NSArray* touches = [[event touchesForView: view] allObjects]; - - for (unsigned int i = 0; i < [touches count]; ++i) - { - UITouch* touch = [touches objectAtIndex: i]; - auto maximumForce = getMaximumTouchForce (touch); - - if ([touch phase] == UITouchPhaseStationary && maximumForce <= 0) - continue; - - auto pos = convertToPointFloat ([touch locationInView: view]); - juce_lastMousePos = pos + getBounds (true).getPosition().toFloat(); - - auto time = getMouseTime (event); - auto touchIndex = currentTouches.getIndexOfTouch (this, touch); - - auto modsToSend = ModifierKeys::currentModifiers; - - auto isUp = [] (MouseEventFlags m) - { - return m == MouseEventFlags::up || m == MouseEventFlags::upAndCancel; - }; - - if (mouseEventFlags == MouseEventFlags::down) - { - if ([touch phase] != UITouchPhaseBegan) - continue; - - ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); - modsToSend = ModifierKeys::currentModifiers; - - // 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, modsToSend.withoutMouseButtons(), - MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation, time, {}, touchIndex); - - if (! isValidPeer (this)) // (in case this component was deleted by the event) - return; - } - else if (isUp (mouseEventFlags)) - { - if (! ([touch phase] == UITouchPhaseEnded || [touch phase] == UITouchPhaseCancelled)) - continue; - - modsToSend = modsToSend.withoutMouseButtons(); - currentTouches.clearTouch (touchIndex); - - if (! currentTouches.areAnyTouchesActive()) - mouseEventFlags = MouseEventFlags::upAndCancel; - } - - if (mouseEventFlags == MouseEventFlags::upAndCancel) - { - currentTouches.clearTouch (touchIndex); - modsToSend = ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons(); - } - - // NB: some devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range: - auto pressure = maximumForce > 0 ? jlimit (0.0001f, 0.9999f, getTouchForce (touch) / maximumForce) - : MouseInputSource::defaultPressure; - - handleMouseEvent (MouseInputSource::InputSourceType::touch, - pos, modsToSend, pressure, MouseInputSource::defaultOrientation, time, { }, touchIndex); - - if (! isValidPeer (this)) // (in case this component was deleted by the event) - return; - - if (isUp (mouseEventFlags)) - { - handleMouseEvent (MouseInputSource::InputSourceType::touch, MouseInputSource::offscreenMousePos, modsToSend, - MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation, time, {}, touchIndex); - - if (! isValidPeer (this)) - return; - } - } -} - -#if JUCE_HAS_IOS_POINTER_SUPPORT -void UIViewComponentPeer::onHover (UIHoverGestureRecognizer* gesture) -{ - auto pos = convertToPointFloat ([gesture locationInView: view]); - juce_lastMousePos = pos + getBounds (true).getPosition().toFloat(); - - handleMouseEvent (MouseInputSource::InputSourceType::touch, - pos, - ModifierKeys::currentModifiers, - MouseInputSource::defaultPressure, MouseInputSource::defaultOrientation, - UIViewComponentPeer::getMouseTime ([[NSProcessInfo processInfo] systemUptime]), - {}); -} - -void UIViewComponentPeer::onScroll (UIPanGestureRecognizer* gesture) -{ - const auto offset = [gesture translationInView: view]; - const auto scale = 0.5f / 256.0f; - - MouseWheelDetails details; - details.deltaX = scale * (float) offset.x; - details.deltaY = scale * (float) offset.y; - details.isReversed = false; - details.isSmooth = true; - details.isInertial = false; - - handleMouseWheel (MouseInputSource::InputSourceType::touch, - convertToPointFloat ([gesture locationInView: view]), - UIViewComponentPeer::getMouseTime ([[NSProcessInfo processInfo] systemUptime]), - details); -} -#endif - -//============================================================================== -void UIViewComponentPeer::viewFocusGain() -{ - if (currentlyFocusedPeer != this) - { - if (ComponentPeer::isValidPeer (currentlyFocusedPeer)) - currentlyFocusedPeer->handleFocusLoss(); - - currentlyFocusedPeer = this; - - handleFocusGain(); - } -} - -void UIViewComponentPeer::viewFocusLoss() -{ - if (currentlyFocusedPeer == this) - { - currentlyFocusedPeer = nullptr; - handleFocusLoss(); - } -} - -bool UIViewComponentPeer::isFocused() const -{ - if (isAppex) - return true; - - return isSharedWindow ? this == currentlyFocusedPeer - : (window != nil && [window isKeyWindow]); -} - -void UIViewComponentPeer::grabFocus() -{ - if (window != nil) - { - [window makeKeyWindow]; - viewFocusGain(); - } -} - -void UIViewComponentPeer::textInputRequired (Point, TextInputTarget&) -{ -} - -static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept -{ - switch (type) - { - case TextInputTarget::textKeyboard: return UIKeyboardTypeAlphabet; - case TextInputTarget::numericKeyboard: return UIKeyboardTypeNumbersAndPunctuation; - case TextInputTarget::decimalKeyboard: return UIKeyboardTypeNumbersAndPunctuation; - case TextInputTarget::urlKeyboard: return UIKeyboardTypeURL; - case TextInputTarget::emailAddressKeyboard: return UIKeyboardTypeEmailAddress; - case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad; - default: jassertfalse; break; - } - - return UIKeyboardTypeDefault; -} - -void UIViewComponentPeer::updateHiddenTextContent (TextInputTarget* target) -{ - view->hiddenTextView.keyboardType = getUIKeyboardType (target->getKeyboardType()); - view->hiddenTextView.text = juceStringToNS (target->getTextInRange (Range (0, target->getHighlightedRegion().getStart()))); - view->hiddenTextView.selectedRange = NSMakeRange ((NSUInteger) target->getHighlightedRegion().getStart(), 0); -} - -BOOL UIViewComponentPeer::textViewReplaceCharacters (Range range, const String& text) -{ - if (auto* target = findCurrentTextInputTarget()) - { - auto currentSelection = target->getHighlightedRegion(); - - if (range.getLength() == 1 && text.isEmpty()) // (detect backspace) - if (currentSelection.isEmpty()) - target->setHighlightedRegion (currentSelection.withStart (currentSelection.getStart() - 1)); - - WeakReference deletionChecker (dynamic_cast (target)); - - if (text == "\r" || text == "\n" || text == "\r\n") - handleKeyPress (KeyPress::returnKey, text[0]); - else - target->insertTextAtCaret (text); - - if (deletionChecker != nullptr) - updateHiddenTextContent (target); - } - - return NO; -} - -void UIViewComponentPeer::globalFocusChanged (Component*) -{ - if (auto* target = findCurrentTextInputTarget()) - { - if (auto* comp = dynamic_cast (target)) - { - auto pos = component.getLocalPoint (comp, Point()); - view->hiddenTextView.frame = CGRectMake (pos.x, pos.y, 0, 0); - - updateHiddenTextContent (target); - [view->hiddenTextView becomeFirstResponder]; - } - } - else - { - [view->hiddenTextView resignFirstResponder]; - } -} - -//============================================================================== -void UIViewComponentPeer::displayLinkCallback() -{ - if (deferredRepaints.isEmpty()) - return; - - auto dispatchRectangles = [this] () - { - // We shouldn't need this preprocessor guard, but when running in the simulator - // CAMetalLayer is flagged as requiring iOS 13 - #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS - if (metalRenderer != nullptr) - { - if (@available (iOS 12, *)) - { - return metalRenderer->drawRectangleList ((CAMetalLayer*) view.layer, - (float) view.contentScaleFactor, - view.frame, - component, - [this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); }, - deferredRepaints); - } - - // The creation of metalRenderer should already be guarded with @available (iOS 12, *). - jassertfalse; - return false; - } - #endif - - for (const auto& r : deferredRepaints) - [view setNeedsDisplayInRect: convertToCGRect (r)]; - - return true; - }; - - if (dispatchRectangles()) - deferredRepaints.clear(); -} - -//============================================================================== -void UIViewComponentPeer::drawRect (CGRect r) -{ - if (r.size.width < 1.0f || r.size.height < 1.0f) - return; - - drawRectWithContext (UIGraphicsGetCurrentContext(), r); -} - -void UIViewComponentPeer::drawRectWithContext (CGContextRef cg, CGRect) -{ - if (! component.isOpaque()) - CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); - - CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, getComponent().getHeight())); - CoreGraphicsContext g (cg, getComponent().getHeight()); - - insideDrawRect = true; - handlePaint (g); - insideDrawRect = false; -} - -bool UIViewComponentPeer::canBecomeKeyWindow() -{ - return (getStyleFlags() & juce::ComponentPeer::windowIgnoresKeyPresses) == 0; -} - -//============================================================================== -void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, bool /*allowMenusAndBars*/) -{ - displays->refresh(); - - if (auto* peer = kioskModeComp->getPeer()) - { - if (auto* uiViewPeer = dynamic_cast (peer)) - [uiViewPeer->controller setNeedsStatusBarAppearanceUpdate]; - - peer->setFullScreen (enableOrDisable); - } -} - -void Desktop::allowedOrientationsChanged() -{ - // if the current orientation isn't allowed anymore then switch orientations - if (! isOrientationEnabled (getCurrentOrientation())) - { - auto newOrientation = [this] - { - for (auto orientation : { upright, upsideDown, rotatedClockwise, rotatedAntiClockwise }) - if (isOrientationEnabled (orientation)) - return orientation; - - // you need to support at least one orientation - jassertfalse; - return upright; - }(); - - NSNumber* value = [NSNumber numberWithInt: (int) Orientations::convertFromJuce (newOrientation)]; - [[UIDevice currentDevice] setValue:value forKey:@"orientation"]; - [value release]; - } -} - -//============================================================================== -void UIViewComponentPeer::repaint (const Rectangle& area) -{ - if (insideDrawRect || ! MessageManager::getInstance()->isThisTheMessageThread()) - { - (new AsyncRepaintMessage (this, area))->post(); - return; - } - - deferredRepaints.add (area.toFloat()); -} - -void UIViewComponentPeer::performAnyPendingRepaintsNow() -{ -} - -ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo) -{ - return new UIViewComponentPeer (*this, styleFlags, (UIView*) windowToAttachTo); -} - -//============================================================================== -const int KeyPress::spaceKey = ' '; -const int KeyPress::returnKey = 0x0d; -const int KeyPress::escapeKey = 0x1b; -const int KeyPress::backspaceKey = 0x7f; -const int KeyPress::leftKey = 0x1000; -const int KeyPress::rightKey = 0x1001; -const int KeyPress::upKey = 0x1002; -const int KeyPress::downKey = 0x1003; -const int KeyPress::pageUpKey = 0x1004; -const int KeyPress::pageDownKey = 0x1005; -const int KeyPress::endKey = 0x1006; -const int KeyPress::homeKey = 0x1007; -const int KeyPress::deleteKey = 0x1008; -const int KeyPress::insertKey = -1; -const int KeyPress::tabKey = 9; -const int KeyPress::F1Key = 0x2001; -const int KeyPress::F2Key = 0x2002; -const int KeyPress::F3Key = 0x2003; -const int KeyPress::F4Key = 0x2004; -const int KeyPress::F5Key = 0x2005; -const int KeyPress::F6Key = 0x2006; -const int KeyPress::F7Key = 0x2007; -const int KeyPress::F8Key = 0x2008; -const int KeyPress::F9Key = 0x2009; -const int KeyPress::F10Key = 0x200a; -const int KeyPress::F11Key = 0x200b; -const int KeyPress::F12Key = 0x200c; -const int KeyPress::F13Key = 0x200d; -const int KeyPress::F14Key = 0x200e; -const int KeyPress::F15Key = 0x200f; -const int KeyPress::F16Key = 0x2010; -const int KeyPress::F17Key = 0x2011; -const int KeyPress::F18Key = 0x2012; -const int KeyPress::F19Key = 0x2013; -const int KeyPress::F20Key = 0x2014; -const int KeyPress::F21Key = 0x2015; -const int KeyPress::F22Key = 0x2016; -const int KeyPress::F23Key = 0x2017; -const int KeyPress::F24Key = 0x2018; -const int KeyPress::F25Key = 0x2019; -const int KeyPress::F26Key = 0x201a; -const int KeyPress::F27Key = 0x201b; -const int KeyPress::F28Key = 0x201c; -const int KeyPress::F29Key = 0x201d; -const int KeyPress::F30Key = 0x201e; -const int KeyPress::F31Key = 0x201f; -const int KeyPress::F32Key = 0x2020; -const int KeyPress::F33Key = 0x2021; -const int KeyPress::F34Key = 0x2022; -const int KeyPress::F35Key = 0x2023; -const int KeyPress::numberPad0 = 0x30020; -const int KeyPress::numberPad1 = 0x30021; -const int KeyPress::numberPad2 = 0x30022; -const int KeyPress::numberPad3 = 0x30023; -const int KeyPress::numberPad4 = 0x30024; -const int KeyPress::numberPad5 = 0x30025; -const int KeyPress::numberPad6 = 0x30026; -const int KeyPress::numberPad7 = 0x30027; -const int KeyPress::numberPad8 = 0x30028; -const int KeyPress::numberPad9 = 0x30029; -const int KeyPress::numberPadAdd = 0x3002a; -const int KeyPress::numberPadSubtract = 0x3002b; -const int KeyPress::numberPadMultiply = 0x3002c; -const int KeyPress::numberPadDivide = 0x3002d; -const int KeyPress::numberPadSeparator = 0x3002e; -const int KeyPress::numberPadDecimalPoint = 0x3002f; -const int KeyPress::numberPadEquals = 0x30030; -const int KeyPress::numberPadDelete = 0x30031; -const int KeyPress::playKey = 0x30000; -const int KeyPress::stopKey = 0x30001; -const int KeyPress::fastForwardKey = 0x30002; -const int KeyPress::rewindKey = 0x30003; - -} // namespace juce diff --git a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm b/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm deleted file mode 100644 index 434b39e6f..000000000 --- a/source/modules/juce_gui_basics/native/juce_ios_Windowing.mm +++ /dev/null @@ -1,844 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - extern bool isIOSAppActive; - - struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules - { - virtual ~AppInactivityCallback() = default; - virtual void appBecomingInactive() = 0; - }; - - // This is an internal list of callbacks (but currently used between modules) - Array appBecomingInactiveCallbacks; -} - -#if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 -@interface JuceAppStartupDelegate : NSObject -#else -@interface JuceAppStartupDelegate : NSObject -#endif -{ - UIBackgroundTaskIdentifier appSuspendTask; -} - -@property (strong, nonatomic) UIWindow *window; -- (id) init; -- (void) dealloc; -- (void) applicationDidFinishLaunching: (UIApplication*) application; -- (void) applicationWillTerminate: (UIApplication*) application; -- (void) applicationDidEnterBackground: (UIApplication*) application; -- (void) applicationWillEnterForeground: (UIApplication*) application; -- (void) applicationDidBecomeActive: (UIApplication*) application; -- (void) applicationWillResignActive: (UIApplication*) application; -- (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*) identifier - completionHandler: (void (^)(void)) completionHandler; -- (void) applicationDidReceiveMemoryWarning: (UIApplication *) application; -#if JUCE_PUSH_NOTIFICATIONS -- (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings; -- (void) application: (UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken; -- (void) application: (UIApplication*) application didFailToRegisterForRemoteNotificationsWithError: (NSError*) error; -- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo; -- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo - fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler; -- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier - forRemoteNotification: (NSDictionary*) userInfo withResponseInfo: (NSDictionary*) responseInfo - completionHandler: (void(^)()) completionHandler; -- (void) application: (UIApplication*) application didReceiveLocalNotification: (UILocalNotification*) notification; -- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier - forLocalNotification: (UILocalNotification*) notification completionHandler: (void(^)()) completionHandler; -- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier - forLocalNotification: (UILocalNotification*) notification withResponseInfo: (NSDictionary*) responseInfo - completionHandler: (void(^)()) completionHandler; -#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 -- (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification - withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler; -- (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response - withCompletionHandler: (void(^)())completionHandler; -#endif -#endif - -@end - -@implementation JuceAppStartupDelegate - - NSObject* _pushNotificationsDelegate; - -- (id) init -{ - self = [super init]; - appSuspendTask = UIBackgroundTaskInvalid; - - #if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - [UNUserNotificationCenter currentNotificationCenter].delegate = self; - #endif - - return self; -} - -- (void) dealloc -{ - [super dealloc]; -} - -- (void) applicationDidFinishLaunching: (UIApplication*) application -{ - ignoreUnused (application); - initialiseJuce_GUI(); - - if (auto* app = JUCEApplicationBase::createInstance()) - { - if (! app->initialiseApp()) - exit (app->shutdownApp()); - } - else - { - jassertfalse; // you must supply an application object for an iOS app! - } -} - -- (void) applicationWillTerminate: (UIApplication*) application -{ - ignoreUnused (application); - JUCEApplicationBase::appWillTerminateByForce(); -} - -- (void) applicationDidEnterBackground: (UIApplication*) application -{ - if (auto* app = JUCEApplicationBase::getInstance()) - { - #if JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK - appSuspendTask = [application beginBackgroundTaskWithName:@"JUCE Suspend Task" expirationHandler:^{ - if (appSuspendTask != UIBackgroundTaskInvalid) - { - [application endBackgroundTask:appSuspendTask]; - appSuspendTask = UIBackgroundTaskInvalid; - } - }]; - - MessageManager::callAsync ([app] { app->suspended(); }); - #else - ignoreUnused (application); - app->suspended(); - #endif - } -} - -- (void) applicationWillEnterForeground: (UIApplication*) application -{ - ignoreUnused (application); - - if (auto* app = JUCEApplicationBase::getInstance()) - app->resumed(); -} - -- (void) applicationDidBecomeActive: (UIApplication*) application -{ - application.applicationIconBadgeNumber = 0; - - isIOSAppActive = true; -} - -- (void) applicationWillResignActive: (UIApplication*) application -{ - ignoreUnused (application); - isIOSAppActive = false; - - for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;) - appBecomingInactiveCallbacks.getReference(i)->appBecomingInactive(); -} - -- (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*)identifier - completionHandler: (void (^)(void))completionHandler -{ - ignoreUnused (application); - URL::DownloadTask::juce_iosURLSessionNotify (nsStringToJuce (identifier)); - completionHandler(); -} - -- (void) applicationDidReceiveMemoryWarning: (UIApplication*) application -{ - ignoreUnused (application); - - if (auto* app = JUCEApplicationBase::getInstance()) - app->memoryWarningReceived(); -} - -- (void) setPushNotificationsDelegateToUse: (NSObject*) delegate -{ - _pushNotificationsDelegate = delegate; -} - -#if JUCE_PUSH_NOTIFICATIONS -- (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings -{ - ignoreUnused (application); - - SEL selector = @selector (application:didRegisterUserNotificationSettings:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: ¬ificationSettings atIndex:3]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken -{ - ignoreUnused (application); - - SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &deviceToken atIndex:3]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application didFailToRegisterForRemoteNotificationsWithError: (NSError*) error -{ - ignoreUnused (application); - - SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &error atIndex:3]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo -{ - ignoreUnused (application); - - SEL selector = @selector (application:didReceiveRemoteNotification:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &userInfo atIndex:3]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo - fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler -{ - ignoreUnused (application); - - SEL selector = @selector (application:didReceiveRemoteNotification:fetchCompletionHandler:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &userInfo atIndex:3]; - [invocation setArgument: &completionHandler atIndex:4]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier - forRemoteNotification: (NSDictionary*) userInfo withResponseInfo: (NSDictionary*) responseInfo - completionHandler: (void(^)()) completionHandler -{ - ignoreUnused (application); - - SEL selector = @selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &identifier atIndex:3]; - [invocation setArgument: &userInfo atIndex:4]; - [invocation setArgument: &responseInfo atIndex:5]; - [invocation setArgument: &completionHandler atIndex:6]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application didReceiveLocalNotification: (UILocalNotification*) notification -{ - ignoreUnused (application); - - SEL selector = @selector (application:didReceiveLocalNotification:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: ¬ification atIndex:3]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier - forLocalNotification: (UILocalNotification*) notification completionHandler: (void(^)()) completionHandler -{ - ignoreUnused (application); - - SEL selector = @selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &identifier atIndex:3]; - [invocation setArgument: ¬ification atIndex:4]; - [invocation setArgument: &completionHandler atIndex:5]; - - [invocation invoke]; - } -} - -- (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier - forLocalNotification: (UILocalNotification*) notification withResponseInfo: (NSDictionary*) responseInfo - completionHandler: (void(^)()) completionHandler -{ - ignoreUnused (application); - - SEL selector = @selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: &application atIndex:2]; - [invocation setArgument: &identifier atIndex:3]; - [invocation setArgument: ¬ification atIndex:4]; - [invocation setArgument: &responseInfo atIndex:5]; - [invocation setArgument: &completionHandler atIndex:6]; - - [invocation invoke]; - } -} - -#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 -- (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification - withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler -{ - ignoreUnused (center); - - SEL selector = @selector (userNotificationCenter:willPresentNotification:withCompletionHandler:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: ¢er atIndex:2]; - [invocation setArgument: ¬ification atIndex:3]; - [invocation setArgument: &completionHandler atIndex:4]; - - [invocation invoke]; - } -} - -- (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response - withCompletionHandler: (void(^)()) completionHandler -{ - ignoreUnused (center); - - SEL selector = @selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:); - - if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) - { - NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; - [invocation setSelector: selector]; - [invocation setTarget: _pushNotificationsDelegate]; - [invocation setArgument: ¢er atIndex:2]; - [invocation setArgument: &response atIndex:3]; - [invocation setArgument: &completionHandler atIndex:4]; - - [invocation invoke]; - } -} -#endif -#endif - -@end - -namespace juce -{ - -int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr); -int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr) -{ - Class delegateClass = (customDelegatePtr != nullptr ? reinterpret_cast (customDelegatePtr) : [JuceAppStartupDelegate class]); - - return UIApplicationMain (argc, const_cast (argv), nil, NSStringFromClass (delegateClass)); -} - -//============================================================================== -void LookAndFeel::playAlertSound() -{ - // TODO -} - -//============================================================================== -class iOSMessageBox -{ -public: - iOSMessageBox (const MessageBoxOptions& opts, - std::unique_ptr&& cb, - bool deleteOnCompletion) - : callback (std::move (cb)), - shouldDeleteThis (deleteOnCompletion) - { - if (currentlyFocusedPeer != nullptr) - { - UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (opts.getTitle()) - message: juceStringToNS (opts.getMessage()) - preferredStyle: UIAlertControllerStyleAlert]; - - addButton (alert, opts.getButtonText (0)); - addButton (alert, opts.getButtonText (1)); - addButton (alert, opts.getButtonText (2)); - - [currentlyFocusedPeer->controller presentViewController: alert - animated: YES - completion: nil]; - } - else - { - // Since iOS8, alert windows need to be associated with a window, so you need to - // have at least one window on screen when you use this - jassertfalse; - } - } - - int getResult() - { - jassert (callback == nullptr); - - JUCE_AUTORELEASEPOOL - { - while (result < 0) - [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; - } - - return result; - } - - void buttonClicked (int buttonIndex) noexcept - { - result = buttonIndex; - - if (callback != nullptr) - callback->modalStateFinished (result); - - if (shouldDeleteThis) - delete this; - } - -private: - void addButton (UIAlertController* alert, const String& text) - { - if (! text.isEmpty()) - { - const auto index = [[alert actions] count]; - - [alert addAction: [UIAlertAction actionWithTitle: juceStringToNS (text) - style: UIAlertActionStyleDefault - handler: ^(UIAlertAction*) { this->buttonClicked ((int) index); }]]; - } - } - - int result = -1; - std::unique_ptr callback; - const bool shouldDeleteThis; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox) -}; - -//============================================================================== -static int showDialog (const MessageBoxOptions& options, - ModalComponentManager::Callback* callbackIn, - AlertWindowMappings::MapFn mapFn) -{ - #if JUCE_MODAL_LOOPS_PERMITTED - if (callbackIn == nullptr) - { - JUCE_AUTORELEASEPOOL - { - jassert (mapFn != nullptr); - - iOSMessageBox messageBox (options, nullptr, false); - return mapFn (messageBox.getResult()); - } - } - #endif - - const auto showBox = [options, callbackIn, mapFn] - { - new iOSMessageBox (options, - AlertWindowMappings::getWrappedCallback (callbackIn, mapFn), - true); - }; - - if (MessageManager::getInstance()->isThisTheMessageThread()) - showBox(); - else - MessageManager::callAsync (showBox); - - return 0; -} - -#if JUCE_MODAL_LOOPS_PERMITTED -void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/) -{ - showDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("OK")), - nullptr, AlertWindowMappings::messageBox); -} - -int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options) -{ - return showDialog (options, nullptr, AlertWindowMappings::noMapping); -} -#endif - -void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - showDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("OK")), - callback, AlertWindowMappings::messageBox); -} - -bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - return showDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("OK")) - .withButton (TRANS("Cancel")), - callback, AlertWindowMappings::okCancel) != 0; -} - -int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - return showDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("Yes")) - .withButton (TRANS("No")) - .withButton (TRANS("Cancel")), - callback, AlertWindowMappings::yesNoCancel); -} - -int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/, - const String& title, const String& message, - Component* /*associatedComponent*/, - ModalComponentManager::Callback* callback) -{ - return showDialog (MessageBoxOptions() - .withTitle (title) - .withMessage (message) - .withButton (TRANS("Yes")) - .withButton (TRANS("No")), - callback, AlertWindowMappings::okCancel); -} - -void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options, - ModalComponentManager::Callback* callback) -{ - showDialog (options, callback, AlertWindowMappings::noMapping); -} - -void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options, - std::function callback) -{ - showAsync (options, ModalCallbackFunction::create (callback)); -} - -//============================================================================== -bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*, std::function) -{ - jassertfalse; // no such thing on iOS! - return false; -} - -bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*, std::function) -{ - jassertfalse; // no such thing on iOS! - return false; -} - -//============================================================================== -void Desktop::setScreenSaverEnabled (const bool isEnabled) -{ - if (! SystemStats::isRunningInAppExtensionSandbox()) - [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled]; -} - -bool Desktop::isScreenSaverEnabled() -{ - if (SystemStats::isRunningInAppExtensionSandbox()) - return true; - - return ! [[UIApplication sharedApplication] isIdleTimerDisabled]; -} - -//============================================================================== -bool juce_areThereAnyAlwaysOnTopWindows() -{ - return false; -} - -//============================================================================== -Image juce_createIconForFile (const File&) -{ - return Image(); -} - -//============================================================================== -void SystemClipboard::copyTextToClipboard (const String& text) -{ - [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text) - forPasteboardType: @"public.text"]; -} - -String SystemClipboard::getTextFromClipboard() -{ - return nsStringToJuce ([[UIPasteboard generalPasteboard] string]); -} - -//============================================================================== -bool MouseInputSource::SourceList::addSource() -{ - addSource (sources.size(), MouseInputSource::InputSourceType::touch); - return true; -} - -bool MouseInputSource::SourceList::canUseTouch() -{ - return true; -} - -bool Desktop::canUseSemiTransparentWindows() noexcept -{ - return true; -} - -bool Desktop::isDarkModeActive() const -{ - #if defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0 - if (@available (iOS 12.0, *)) - return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark; - #endif - - return false; -} - -class Desktop::NativeDarkModeChangeDetectorImpl -{ -public: - NativeDarkModeChangeDetectorImpl() - { - static DelegateClass delegateClass; - - delegate = [delegateClass.createInstance() init]; - object_setInstanceVariable (delegate, "owner", this); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - [[NSNotificationCenter defaultCenter] addObserver: delegate - selector: @selector (darkModeChanged:) - name: UIViewComponentPeer::getDarkModeNotificationName() - object: nil]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - - ~NativeDarkModeChangeDetectorImpl() - { - object_setInstanceVariable (delegate, "owner", nullptr); - [[NSNotificationCenter defaultCenter] removeObserver: delegate]; - [delegate release]; - } - - void darkModeChanged() - { - Desktop::getInstance().darkModeChanged(); - } - -private: - struct DelegateClass : public ObjCClass - { - DelegateClass() : ObjCClass ("JUCEDelegate_") - { - addIvar ("owner"); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (darkModeChanged:), darkModeChanged); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - registerClass(); - } - - static void darkModeChanged (id self, SEL, NSNotification*) - { - if (auto* owner = getIvar (self, "owner")) - owner->darkModeChanged(); - } - }; - - id delegate = nil; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl) -}; - -std::unique_ptr Desktop::createNativeDarkModeChangeDetectorImpl() -{ - return std::make_unique(); -} - -Point MouseInputSource::getCurrentRawMousePosition() -{ - return juce_lastMousePos; -} - -void MouseInputSource::setRawMousePosition (Point) -{ -} - -double Desktop::getDefaultMasterScale() -{ - return 1.0; -} - -Desktop::DisplayOrientation Desktop::getCurrentOrientation() const -{ - UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait - : getWindowOrientation(); - - return Orientations::convertToJuce (orientation); -} - -// The most straightforward way of retrieving the screen area available to an iOS app -// seems to be to create a new window (which will take up all available space) and to -// query its frame. -struct TemporaryWindow -{ - UIWindow* window = [[UIWindow alloc] init]; - ~TemporaryWindow() noexcept { [window release]; } -}; - -static Rectangle getRecommendedWindowBounds() -{ - return convertToRectInt (TemporaryWindow().window.frame); -} - -static BorderSize getSafeAreaInsets (float masterScale) -{ - #if defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (@available (iOS 11.0, *)) - { - UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets; - - auto getInset = [&] (CGFloat original) { return roundToInt (original / masterScale); }; - - return { getInset (safeInsets.top), getInset (safeInsets.left), - getInset (safeInsets.bottom), getInset (safeInsets.right) }; - } - #endif - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height); - - return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 }; -} - -void Displays::findDisplays (float masterScale) -{ - JUCE_AUTORELEASEPOOL - { - UIScreen* s = [UIScreen mainScreen]; - - Display d; - d.totalArea = convertToRectInt ([s bounds]) / masterScale; - d.userArea = getRecommendedWindowBounds() / masterScale; - d.safeAreaInsets = getSafeAreaInsets (masterScale); - d.isMain = true; - d.scale = masterScale * s.scale; - d.dpi = 160 * d.scale; - - displays.add (d); - } -} - -} // namespace juce diff --git a/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp b/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp index 24950ac22..df8132b69 100644 --- a/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp +++ b/source/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp @@ -273,6 +273,8 @@ std::shared_ptr FileChooser::showPlatformDialog (FileChooser #if JUCE_MODAL_LOOPS_PERMITTED return std::make_shared (owner, flags); #else + ignoreUnused (owner); + ignoreUnused (flags); return nullptr; #endif } diff --git a/source/modules/juce_gui_basics/native/juce_mac_CGMetalLayerRenderer.h b/source/modules/juce_gui_basics/native/juce_mac_CGMetalLayerRenderer.h index 9b266e784..f09f29311 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_CGMetalLayerRenderer.h +++ b/source/modules/juce_gui_basics/native/juce_mac_CGMetalLayerRenderer.h @@ -30,13 +30,37 @@ namespace juce { //============================================================================== +template class CoreGraphicsMetalLayerRenderer { public: //============================================================================== - CoreGraphicsMetalLayerRenderer (CAMetalLayer* layer, const Component& comp) + CoreGraphicsMetalLayerRenderer (ViewType* view, const Component& comp) { device.reset (MTLCreateSystemDefaultDevice()); + commandQueue.reset ([device.get() newCommandQueue]); + + attach (view, comp); + } + + ~CoreGraphicsMetalLayerRenderer() + { + if (memoryBlitCommandBuffer != nullptr) + { + stopGpuCommandSubmission = true; + [memoryBlitCommandBuffer.get() waitUntilCompleted]; + } + } + + void attach (ViewType* view, const Component& comp) + { + #if JUCE_MAC + view.wantsLayer = YES; + view.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft; + view.layer = [CAMetalLayer layer]; + #endif + + auto layer = (CAMetalLayer*) view.layer; layer.device = device.get(); layer.framebufferOnly = NO; @@ -44,33 +68,50 @@ public: layer.opaque = comp.isOpaque(); layer.allowsNextDrawableTimeout = NO; - commandQueue.reset ([device.get() newCommandQueue]); + attachedView = view; + doSynchronousRender = true; + } + + void detach() + { + #if JUCE_MAC + attachedView.wantsLayer = NO; + attachedView.layer = nil; + #endif - memoryBlitEvent.reset ([device.get() newSharedEvent]); + attachedView = nullptr; } - ~CoreGraphicsMetalLayerRenderer() + bool isAttachedToView (ViewType* view) const { - stopGpuCommandSubmission = true; - [memoryBlitCommandBuffer.get() waitUntilCompleted]; + return view == attachedView && attachedView != nullptr; } template - bool drawRectangleList (CAMetalLayer* layer, + bool drawRectangleList (ViewType* view, float scaleFactor, CGRect viewFrame, const Component& comp, Callback&& drawRectWithContext, const RectangleList& dirtyRegions) { - if (resources != nullptr) - { - // If we haven't finished blitting the CPU texture to the GPU then - // report that we have been unable to draw anything. - if (memoryBlitEvent.get().signaledValue != memoryBlitCounter + 1) - return false; + auto layer = (CAMetalLayer*) view.layer; - ++memoryBlitCounter; + if (memoryBlitCommandBuffer != nullptr) + { + switch ([memoryBlitCommandBuffer.get() status]) + { + case MTLCommandBufferStatusNotEnqueued: + case MTLCommandBufferStatusEnqueued: + case MTLCommandBufferStatusCommitted: + case MTLCommandBufferStatusScheduled: + // If we haven't finished blitting the CPU texture to the GPU then + // report that we have been unable to draw anything. + return false; + case MTLCommandBufferStatusCompleted: + case MTLCommandBufferStatusError: + break; + } } layer.contentsScale = scaleFactor; @@ -80,7 +121,7 @@ public: const auto componentHeight = comp.getHeight(); - if (! CGSizeEqualToSize (layer.drawableSize, transformedFrameSize)) + if (resources == nullptr || ! CGSizeEqualToSize (layer.drawableSize, transformedFrameSize)) { layer.drawableSize = transformedFrameSize; resources = std::make_unique (device.get(), layer, componentHeight); @@ -108,68 +149,79 @@ public: CGContextRestoreGState (cgContext); } - auto cpuTexture = resources->getCpuTexture(); + resources->signalBufferModifiedByCpu(); + + auto sharedTexture = resources->getSharedTexture(); - memoryBlitCommandBuffer.reset ([commandQueue.get() commandBuffer]); + auto encodeBlit = [] (id commandBuffer, + id source, + id destination) + { + auto blitCommandEncoder = [commandBuffer blitCommandEncoder]; + [blitCommandEncoder copyFromTexture: source + sourceSlice: 0 + sourceLevel: 0 + sourceOrigin: MTLOrigin{} + sourceSize: MTLSize { source.width, source.height, 1 } + toTexture: destination + destinationSlice: 0 + destinationLevel: 0 + destinationOrigin: MTLOrigin{}]; + [blitCommandEncoder endEncoding]; + }; - // Command buffers are usually considered temporary, and are automatically released by - // the operating system when the rendering pipeline is finsihed. However, we want to keep - // this one alive so that we can wait for pipeline completion in the destructor. - [memoryBlitCommandBuffer.get() retain]; + if (doSynchronousRender) + { + @autoreleasepool + { + id commandBuffer = [commandQueue.get() commandBuffer]; - auto blitCommandEncoder = [memoryBlitCommandBuffer.get() blitCommandEncoder]; - [blitCommandEncoder copyFromTexture: cpuTexture - sourceSlice: 0 - sourceLevel: 0 - sourceOrigin: MTLOrigin{} - sourceSize: MTLSize { cpuTexture.width, cpuTexture.height, 1 } - toTexture: gpuTexture - destinationSlice: 0 - destinationLevel: 0 - destinationOrigin: MTLOrigin{}]; - [blitCommandEncoder endEncoding]; + id drawable = [layer nextDrawable]; + encodeBlit (commandBuffer, sharedTexture, drawable.texture); - // Signal that the GPU has finished using the CPU texture - [memoryBlitCommandBuffer.get() encodeSignalEvent: memoryBlitEvent.get() - value: memoryBlitCounter + 1]; + [commandBuffer presentDrawable: drawable]; + [commandBuffer commit]; + } - [memoryBlitCommandBuffer.get() addScheduledHandler: ^(id) + doSynchronousRender = false; + } + else { - // We're on a Metal thread, so we can make a blocking nextDrawable call - // without stalling the message thread. + // Command buffers are usually considered temporary, and are automatically released by + // the operating system when the rendering pipeline is finsihed. However, we want to keep + // this one alive so that we can wait for pipeline completion in the destructor. + memoryBlitCommandBuffer.reset ([[commandQueue.get() commandBuffer] retain]); - // Check if we can do an early exit. - if (stopGpuCommandSubmission) - return; + encodeBlit (memoryBlitCommandBuffer.get(), sharedTexture, gpuTexture); - @autoreleasepool + [memoryBlitCommandBuffer.get() addScheduledHandler: ^(id) { - id drawable = [layer nextDrawable]; + // We're on a Metal thread, so we can make a blocking nextDrawable call + // without stalling the message thread. - id presentationCommandBuffer = [commandQueue.get() commandBuffer]; - - auto presentationBlitCommandEncoder = [presentationCommandBuffer blitCommandEncoder]; - [presentationBlitCommandEncoder copyFromTexture: gpuTexture - sourceSlice: 0 - sourceLevel: 0 - sourceOrigin: MTLOrigin{} - sourceSize: MTLSize { gpuTexture.width, gpuTexture.height, 1 } - toTexture: drawable.texture - destinationSlice: 0 - destinationLevel: 0 - destinationOrigin: MTLOrigin{}]; - [presentationBlitCommandEncoder endEncoding]; - - [presentationCommandBuffer addScheduledHandler: ^(id) + // Check if we can do an early exit. + if (stopGpuCommandSubmission) + return; + + @autoreleasepool { - [drawable present]; - }]; + id drawable = [layer nextDrawable]; - [presentationCommandBuffer commit]; - } - }]; + id presentationCommandBuffer = [commandQueue.get() commandBuffer]; + + encodeBlit (presentationCommandBuffer, gpuTexture, drawable.texture); - [memoryBlitCommandBuffer.get() commit]; + [presentationCommandBuffer addScheduledHandler: ^(id) + { + [drawable present]; + }]; + + [presentationCommandBuffer commit]; + } + }]; + + [memoryBlitCommandBuffer.get() commit]; + } return true; } @@ -181,18 +233,6 @@ private: return ((n + alignment - 1) / alignment) * alignment; } - //============================================================================== - struct TextureDeleter - { - void operator() (id texture) const noexcept - { - [texture setPurgeableState: MTLPurgeableStateEmpty]; - [texture release]; - } - }; - - using TextureUniquePtr = std::unique_ptr>, TextureDeleter>; - //============================================================================== class GpuTexturePool { @@ -206,12 +246,12 @@ private: id take() const { auto iter = std::find_if (textureCache.begin(), textureCache.end(), - [] (const TextureUniquePtr& t) { return [t.get() retainCount] == 1; }); + [] (const ObjCObjectHandle>& t) { return [t.get() retainCount] == 1; }); return iter == textureCache.end() ? nullptr : (*iter).get(); } private: - std::array textureCache; + std::array>, 3> textureCache; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GpuTexturePool) JUCE_DECLARE_NON_MOVEABLE (GpuTexturePool) @@ -227,21 +267,31 @@ private: const auto allocationSize = cpuRenderMemory.ensureSize (bytesPerRow * (size_t) layer.drawableSize.height); - ObjCObjectHandle> buffer { [metalDevice newBufferWithBytesNoCopy: cpuRenderMemory.get() - length: allocationSize - options: MTLResourceStorageModeShared - deallocator: nullptr] }; + buffer.reset ([metalDevice newBufferWithBytesNoCopy: cpuRenderMemory.get() + length: allocationSize + options: + #if JUCE_MAC + MTLResourceStorageModeManaged + #else + MTLResourceStorageModeShared + #endif + deallocator: nullptr]); auto* textureDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: layer.pixelFormat width: (NSUInteger) layer.drawableSize.width height: (NSUInteger) layer.drawableSize.height mipmapped: NO]; - textureDesc.storageMode = buffer.get().storageMode; + textureDesc.storageMode = + #if JUCE_MAC + MTLStorageModeManaged; + #else + MTLStorageModeShared; + #endif textureDesc.usage = MTLTextureUsageShaderRead; - cpuTexture.reset ([buffer.get() newTextureWithDescriptor: textureDesc - offset: 0 - bytesPerRow: bytesPerRow]); + sharedTexture.reset ([buffer.get() newTextureWithDescriptor: textureDesc + offset: 0 + bytesPerRow: bytesPerRow]); cgContext.reset (CGBitmapContextCreate (cpuRenderMemory.get(), (size_t) layer.drawableSize.width, @@ -258,9 +308,16 @@ private: gpuTexturePool = std::make_unique (metalDevice, textureDesc); } - CGContextRef getCGContext() const noexcept { return cgContext.get(); } - id getCpuTexture() const noexcept { return cpuTexture.get(); } - id getGpuTexture() noexcept { return gpuTexturePool == nullptr ? nullptr : gpuTexturePool->take(); } + CGContextRef getCGContext() const noexcept { return cgContext.get(); } + id getSharedTexture() const noexcept { return sharedTexture.get(); } + id getGpuTexture() noexcept { return gpuTexturePool == nullptr ? nullptr : gpuTexturePool->take(); } + + void signalBufferModifiedByCpu() + { + #if JUCE_MAC + [buffer.get() didModifyRange: { 0, buffer.get().length }]; + #endif + } private: class AlignedMemory @@ -318,7 +375,8 @@ private: detail::ContextPtr cgContext; - TextureUniquePtr cpuTexture; + ObjCObjectHandle> buffer; + ObjCObjectHandle> sharedTexture; std::unique_ptr gpuTexturePool; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Resources) @@ -326,14 +384,15 @@ private: }; //============================================================================== + ViewType* attachedView = nullptr; + bool doSynchronousRender = false; + std::unique_ptr resources; ObjCObjectHandle> device; ObjCObjectHandle> commandQueue; ObjCObjectHandle> memoryBlitCommandBuffer; - ObjCObjectHandle> memoryBlitEvent; - uint64_t memoryBlitCounter = 0; std::atomic stopGpuCommandSubmission { false }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsMetalLayerRenderer) diff --git a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm index 434df96ce..5d15acf60 100644 --- a/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm +++ b/source/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm @@ -40,6 +40,29 @@ using CheckEventBlockedByModalComps = bool (*) (NSEvent*); extern CheckEventBlockedByModalComps isEventBlockedByModalComps; //============================================================================== +static void resetTrackingArea (NSView* view) +{ + const auto trackingAreas = [view trackingAreas]; + + jassert ([trackingAreas count] <= 1); + + for (NSTrackingArea* area in trackingAreas) + [view removeTrackingArea: area]; + + const auto options = NSTrackingMouseEnteredAndExited + | NSTrackingMouseMoved + | NSTrackingEnabledDuringMouseDrag + | NSTrackingActiveAlways + | NSTrackingInVisibleRect; + + const NSUniquePtr trackingArea { [[NSTrackingArea alloc] initWithRect: [view bounds] + options: options + owner: view + userInfo: nil] }; + + [view addTrackingArea: trackingArea.get()]; +} + static constexpr int translateVirtualToAsciiKeyCode (int keyCode) noexcept { switch (keyCode) @@ -119,16 +142,7 @@ public: [view registerForDraggedTypes: getSupportedDragTypes()]; - const auto options = NSTrackingMouseEnteredAndExited - | NSTrackingMouseMoved - | NSTrackingEnabledDuringMouseDrag - | NSTrackingActiveAlways - | NSTrackingInVisibleRect; - const NSUniquePtr trackingArea { [[NSTrackingArea alloc] initWithRect: r - options: options - owner: view - userInfo: nil] }; - [view addTrackingArea: trackingArea.get()]; + resetTrackingArea (view); notificationCenter = [NSNotificationCenter defaultCenter]; @@ -139,7 +153,11 @@ public: [view setPostsFrameChangedNotifications: YES]; - #if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING + #if JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS + if (@available (macOS 10.14, *)) + metalRenderer = std::make_unique> (view, getComponent()); + #endif if ((windowStyleFlags & ComponentPeer::windowRequiresSynchronousCoreGraphicsRendering) == 0) { if (@available (macOS 10.8, *)) @@ -148,7 +166,7 @@ public: [[view layer] setDrawsAsynchronously: YES]; } } - #endif + #endif createCVDisplayLink(); @@ -348,7 +366,10 @@ public: } if (oldViewSize.width != r.size.width || oldViewSize.height != r.size.height) + { + numFramesToSkipMetalRenderer = 5; [view setNeedsDisplay: true]; + } } Rectangle getBounds (const bool global) const @@ -1062,54 +1083,41 @@ public: if (msSinceLastRepaint < minimumRepaintInterval && shouldThrottleRepaint()) return; - #if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS - // We require macOS 10.14 to use the Metal layer renderer - if (@available (macOS 10.14, *)) + if (metalRenderer != nullptr) { - const auto& comp = getComponent(); + const auto compBounds = getComponent().getLocalBounds().toFloat(); // If we are resizing we need to fall back to synchronous drawing to avoid artefacts - if (areAnyWindowsInLiveResize()) + if ([window inLiveResize] || numFramesToSkipMetalRenderer > 0) { - if (metalRenderer != nullptr) + if (metalRenderer->isAttachedToView (view)) { - metalRenderer.reset(); - view.wantsLayer = NO; - view.layer = nil; - deferredRepaints = comp.getLocalBounds().toFloat(); + metalRenderer->detach(); + deferredRepaints = compBounds; } + + if (numFramesToSkipMetalRenderer > 0) + --numFramesToSkipMetalRenderer; } else { - if (metalRenderer == nullptr) + if (! metalRenderer->isAttachedToView (view)) { - view.wantsLayer = YES; - view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize; - view.layerContentsPlacement = NSViewLayerContentsPlacementTopLeft; - view.layer = [CAMetalLayer layer]; - metalRenderer = std::make_unique ((CAMetalLayer*) view.layer, getComponent()); - deferredRepaints = comp.getLocalBounds().toFloat(); + metalRenderer->attach (view, getComponent()); + deferredRepaints = compBounds; } } } - #endif - auto dispatchRectangles = [this] () + auto dispatchRectangles = [this] { - #if USE_COREGRAPHICS_RENDERING - if (@available (macOS 10.14, *)) - { - if (metalRenderer != nullptr) - { - return metalRenderer->drawRectangleList ((CAMetalLayer*) view.layer, - (float) [[view window] backingScaleFactor], - view.frame, - getComponent(), - [this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); }, - deferredRepaints); - } - } - #endif + if (metalRenderer != nullptr && metalRenderer->isAttachedToView (view)) + return metalRenderer->drawRectangleList (view, + (float) [[view window] backingScaleFactor], + view.frame, + getComponent(), + [this] (CGContextRef ctx, CGRect r) { drawRectWithContext (ctx, r); }, + deferredRepaints); for (auto& i : deferredRepaints) [view setNeedsDisplayInRect: makeNSRect (i)]; @@ -1881,9 +1889,8 @@ private: CVDisplayLinkRef displayLink = nullptr; dispatch_source_t displaySource = nullptr; - #if USE_COREGRAPHICS_RENDERING - std::unique_ptr metalRenderer; - #endif + int numFramesToSkipMetalRenderer = 0; + std::unique_ptr> metalRenderer; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponentPeer) }; @@ -1930,6 +1937,7 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper> { addMethod (@selector (isOpaque), isOpaque); addMethod (@selector (drawRect:), drawRect); + addMethod (@selector (updateTrackingAreas), updateTrackingAreas); addMethod (@selector (mouseDown:), mouseDown); addMethod (@selector (mouseUp:), mouseUp); addMethod (@selector (mouseDragged:), mouseDragged); @@ -2013,6 +2021,13 @@ struct JuceNSViewClass : public NSViewComponentPeerWrapper> } private: + static void updateTrackingAreas (id self, SEL) + { + sendSuperclassMessage (self, @selector (updateTrackingAreas)); + + resetTrackingArea (static_cast (self)); + } + static void mouseDown (id self, SEL s, NSEvent* ev) { if (JUCEApplicationBase::isStandaloneApp()) diff --git a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index 830f037da..7a012c39c 100644 --- a/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/source/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -1264,93 +1264,6 @@ __CRT_UUID_DECL (juce::ITipInvocation, 0x37c994e7, 0x432b, 0x4834, 0xa2, 0xf7, 0 namespace juce { -struct OnScreenKeyboard : public DeletedAtShutdown, - private Timer -{ - void activate() - { - shouldBeActive = true; - startTimer (10); - } - - void deactivate() - { - shouldBeActive = false; - startTimer (10); - } - - JUCE_DECLARE_SINGLETON_SINGLETHREADED (OnScreenKeyboard, false) - -private: - OnScreenKeyboard() - { - tipInvocation.CoCreateInstance (ITipInvocation::getCLSID(), CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER); - } - - ~OnScreenKeyboard() override - { - clearSingletonInstance(); - } - - void timerCallback() override - { - stopTimer(); - - if (reentrant || tipInvocation == nullptr) - return; - - const ScopedValueSetter setter (reentrant, true, false); - - auto isActive = isKeyboardVisible(); - - if (isActive != shouldBeActive) - { - if (! isActive) - { - tipInvocation->Toggle (GetDesktopWindow()); - } - else - { - if (auto hwnd = FindWindow (L"IPTip_Main_Window", nullptr)) - PostMessage (hwnd, WM_SYSCOMMAND, (int) SC_CLOSE, 0); - } - } - } - - bool isVisible() - { - if (auto hwnd = FindWindowEx (nullptr, nullptr, L"ApplicationFrameWindow", nullptr)) - return FindWindowEx (hwnd, nullptr, L"Windows.UI.Core.CoreWindow", L"Microsoft Text Input Application") != nullptr; - - return false; - } - - bool isVisibleLegacy() - { - if (auto hwnd = FindWindow (L"IPTip_Main_Window", nullptr)) - { - auto style = GetWindowLong (hwnd, GWL_STYLE); - return (style & WS_DISABLED) == 0 && (style & WS_VISIBLE) != 0; - } - - return false; - } - - bool isKeyboardVisible() - { - if (isVisible()) - return true; - - // isVisible() may fail on Win10 versions < 1709 so try the old method too - return isVisibleLegacy(); - } - - bool shouldBeActive = false, reentrant = false; - ComSmartPtr tipInvocation; -}; - -JUCE_IMPLEMENT_SINGLETON (OnScreenKeyboard) - //============================================================================== struct HSTRING_PRIVATE; typedef HSTRING_PRIVATE* HSTRING; @@ -1430,30 +1343,6 @@ struct UWPUIViewSettings } } - bool isTabletModeActivatedForWindow (::HWND hWnd) const - { - if (viewSettingsInterop == nullptr) - return false; - - ComSmartPtr viewSettings; - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") - - if (viewSettingsInterop->GetForWindow (hWnd, __uuidof (IUIViewSettings), - (void**) viewSettings.resetAndGetPointerAddress()) == S_OK - && viewSettings != nullptr) - { - IUIViewSettings::UserInteractionMode mode; - - if (viewSettings->GetUserInteractionMode (&mode) == S_OK) - return mode == IUIViewSettings::Touch; - } - - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - return false; - } - private: //============================================================================== struct ComBaseModule @@ -1483,7 +1372,6 @@ private: WindowsDeleteStringFuncPtr deleteHString; }; -#if 0 //============================================================================== static HMONITOR getMonitorFromOutput (ComSmartPtr output) { @@ -1685,7 +1573,7 @@ public: threads.end()); } - JUCE_DECLARE_SINGLETON_SINGLETHREADED (VBlankDispatcher, true) + JUCE_DECLARE_SINGLETON_SINGLETHREADED (VBlankDispatcher, false) private: //============================================================================== @@ -1718,23 +1606,18 @@ private: } //============================================================================== - #if 0 std::vector> adapters; Threads threads; - #endif JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VBlankDispatcher) JUCE_DECLARE_NON_MOVEABLE (VBlankDispatcher) }; JUCE_IMPLEMENT_SINGLETON (VBlankDispatcher) -#endif //============================================================================== class HWNDComponentPeer : public ComponentPeer, - #if 0 private VBlankListener, - #endif private Timer #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client , public ModifierKeyReceiver @@ -1759,8 +1642,6 @@ public: setTitle (component.getName()); updateShadower(); - OnScreenKeyboard::getInstance(); - getNativeRealtimeModifiers = [] { HWNDComponentPeer::updateKeyModifiers(); @@ -1775,19 +1656,13 @@ public: return ModifierKeys::currentModifiers; }; - #if 0 if (updateCurrentMonitor()) VBlankDispatcher::getInstance()->updateDisplay (*this, currentMonitor); - #else - updateCurrentMonitor(); - #endif } ~HWNDComponentPeer() override { - #if 0 VBlankDispatcher::getInstance()->removeListener (*this); - #endif // do this first to avoid messages arriving for this window before it's destroyed JuceWindowIdentifier::setAsJUCEWindow (hwnd, false); @@ -2132,16 +2007,22 @@ public: void textInputRequired (Point, TextInputTarget&) override { if (! hasCreatedCaret) + hasCreatedCaret = CreateCaret (hwnd, (HBITMAP) 1, 0, 0); + + if (hasCreatedCaret) { - hasCreatedCaret = true; - CreateCaret (hwnd, (HBITMAP) 1, 0, 0); + SetCaretPos (0, 0); + ShowCaret (hwnd); } - ShowCaret (hwnd); - SetCaretPos (0, 0); + ImmAssociateContext (hwnd, nullptr); - if (uwpViewSettings.isTabletModeActivatedForWindow (hwnd)) - OnScreenKeyboard::getInstance()->activate(); + // MSVC complains about the nullptr argument, but the docs for this + // function say that the second argument is ignored when the third + // argument is IACE_DEFAULT. + JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6387) + ImmAssociateContextEx (hwnd, nullptr, IACE_DEFAULT); + JUCE_END_IGNORE_WARNINGS_MSVC } void closeInputMethodContext() override @@ -2153,8 +2034,10 @@ public: { closeInputMethodContext(); - if (uwpViewSettings.isTabletModeActivatedForWindow (hwnd)) - OnScreenKeyboard::getInstance()->deactivate(); + ImmAssociateContext (hwnd, nullptr); + + if (std::exchange (hasCreatedCaret, false)) + DestroyCaret(); } void repaint (const Rectangle& area) override @@ -2188,13 +2071,11 @@ public: } } - #if 0 //============================================================================== void onVBlank() override { dispatchDeferredRepaints(); } - #endif //============================================================================== static HWNDComponentPeer* getOwnerOfWindow (HWND h) noexcept @@ -3793,12 +3674,8 @@ private: handleMovedOrResized(); - #if 0 if (updateCurrentMonitor()) VBlankDispatcher::getInstance()->updateDisplay (*this, currentMonitor); - #else - updateCurrentMonitor(); - #endif return ! dontRepaint; // to allow non-accelerated openGL windows to draw themselves correctly. } @@ -3954,14 +3831,10 @@ private: .getDisplayForRect (component.getScreenBounds())->userArea), SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING); - #if 0 auto* dispatcher = VBlankDispatcher::getInstance(); dispatcher->reconfigureDisplays(); updateCurrentMonitor(); dispatcher->updateDisplay (*this, currentMonitor); - #else - updateCurrentMonitor(); - #endif } //============================================================================== @@ -4427,13 +4300,18 @@ private: { if (compositionInProgress && ! windowIsActive) { - compositionInProgress = false; - if (HIMC hImc = ImmGetContext (hWnd)) { ImmNotifyIME (hImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); ImmReleaseContext (hWnd, hImc); } + + // If the composition is still in progress, calling ImmNotifyIME may call back + // into handleComposition to let us know that the composition has finished. + // We need to set compositionInProgress *after* calling handleComposition, so that + // the text replaces the current selection, rather than being inserted after the + // caret. + compositionInProgress = false; } } diff --git a/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp b/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp index ac7de0013..3291db8a3 100644 --- a/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp +++ b/source/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp @@ -413,6 +413,31 @@ namespace Keys static bool capsLock = false; static char keyStates [32]; static constexpr int extendedKeyModifier = 0x10000000; + static bool modifierKeysAreStale = false; + + static void refreshStaleModifierKeys() + { + if (modifierKeysAreStale) + { + XWindowSystem::getInstance()->getNativeRealtimeModifiers(); + modifierKeysAreStale = false; + } + } + + // Call this function when only the mouse keys need to be refreshed e.g. when the event + // parameter already has information about the keys. + static void refreshStaleMouseKeys() + { + if (modifierKeysAreStale) + { + const auto oldMods = ModifierKeys::currentModifiers; + XWindowSystem::getInstance()->getNativeRealtimeModifiers(); + ModifierKeys::currentModifiers = oldMods.withoutMouseButtons() + .withFlags (ModifierKeys::currentModifiers.withOnlyMouseButtons() + .getRawFlags()); + modifierKeysAreStale = false; + } + } } const int KeyPress::spaceKey = XK_space & 0xff; @@ -1747,17 +1772,17 @@ void XWindowSystem::setBounds (::Window windowH, Rectangle newBounds, bool X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints.get()); } - const auto windowBorder = [&]() -> BorderSize + const auto nativeWindowBorder = [&]() -> BorderSize { if (const auto& frameSize = peer->getFrameSizeIfPresent()) - return *frameSize; + return frameSize->multipliedBy (peer->getPlatformScaleFactor()); return {}; }(); X11Symbols::getInstance()->xMoveResizeWindow (display, windowH, - newBounds.getX() - windowBorder.getLeft(), - newBounds.getY() - windowBorder.getTop(), + newBounds.getX() - nativeWindowBorder.getLeft(), + newBounds.getY() - nativeWindowBorder.getTop(), (unsigned int) newBounds.getWidth(), (unsigned int) newBounds.getHeight()); } @@ -2448,6 +2473,18 @@ ModifierKeys XWindowSystem::getNativeRealtimeModifiers() const ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withoutMouseButtons().withFlags (mouseMods); + // We are keeping track of the state of modifier keys and mouse buttons with the assumption that + // for every mouse down we are going to receive a mouse up etc. + // + // This assumption is broken when getNativeRealtimeModifiers() is called. If for example we call + // this function when the mouse cursor is in another application and the mouse button happens to + // be down, then its represented state in currentModifiers may remain down indefinitely, since + // we aren't going to receive an event when it's released. + // + // We mark this state in this variable, and we can restore synchronization when our window + // receives an event again. + Keys::modifierKeysAreStale = true; + return ModifierKeys::currentModifiers; } @@ -3314,6 +3351,7 @@ void XWindowSystem::handleWindowMessage (LinuxComponentPeer* peer, XEvent& event void XWindowSystem::handleKeyPressEvent (LinuxComponentPeer* peer, XKeyEvent& keyEvent) const { auto oldMods = ModifierKeys::currentModifiers; + Keys::refreshStaleModifierKeys(); char utf8 [64] = { 0 }; juce_wchar unicodeChar = 0; @@ -3544,6 +3582,7 @@ void XWindowSystem::handleButtonReleaseEvent (LinuxComponentPeer* peer, const XB void XWindowSystem::handleMotionNotifyEvent (LinuxComponentPeer* peer, const XPointerMovedEvent& movedEvent) const { updateKeyModifiers ((int) movedEvent.state); + Keys::refreshStaleMouseKeys(); auto& dragState = dragAndDropStateMap[peer]; diff --git a/source/modules/juce_gui_basics/widgets/juce_Label.cpp b/source/modules/juce_gui_basics/widgets/juce_Label.cpp index 29726544f..9f24a20b2 100644 --- a/source/modules/juce_gui_basics/widgets/juce_Label.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_Label.cpp @@ -208,9 +208,6 @@ void Label::editorShown (TextEditor* textEditor) void Label::editorAboutToBeHidden (TextEditor* textEditor) { - if (auto* peer = getPeer()) - peer->dismissPendingTextInput(); - Component::BailOutChecker checker (this); listeners.callChecked (checker, [this, textEditor] (Label::Listener& l) { l.editorHidden (this, *textEditor); }); diff --git a/source/modules/juce_gui_basics/widgets/juce_Slider.cpp b/source/modules/juce_gui_basics/widgets/juce_Slider.cpp index 2573befcd..1e9b6ad70 100644 --- a/source/modules/juce_gui_basics/widgets/juce_Slider.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_Slider.cpp @@ -26,6 +26,14 @@ namespace juce { +static double getStepSize (const Slider& slider) +{ + const auto interval = slider.getInterval(); + + return interval != 0.0 ? interval + : slider.getRange().getLength() * 0.01; +} + class Slider::Pimpl : public AsyncUpdater, // this needs to be public otherwise it will cause an // error when JUCE_DLL_BUILD=1 private Value::Listener @@ -991,6 +999,38 @@ public: popupDisplay.reset(); } + bool keyPressed (const KeyPress& key) + { + if (key.getModifiers().isAnyModifierKeyDown()) + return false; + + const auto getInterval = [this] + { + if (auto* accessibility = owner.getAccessibilityHandler()) + if (auto* valueInterface = accessibility->getValueInterface()) + return valueInterface->getRange().getInterval(); + + return getStepSize (owner); + }; + + const auto valueChange = [&] + { + if (key == KeyPress::rightKey || key == KeyPress::upKey) + return getInterval(); + + if (key == KeyPress::leftKey || key == KeyPress::downKey) + return -getInterval(); + + return 0.0; + }(); + + if (valueChange == 0.0) + return false; + + setValue (getValue() + valueChange, sendNotificationSync); + return true; + } + void showPopupDisplay() { if (style == IncDecButtons) @@ -1661,6 +1701,9 @@ void Slider::mouseExit (const MouseEvent&) { pimpl->mouseExit(); } // it is shown when dragging the mouse over a slider and releasing void Slider::mouseEnter (const MouseEvent&) { pimpl->mouseMove(); } +/** @internal */ +bool Slider::keyPressed (const KeyPress& k) { return pimpl->keyPressed (k); } + void Slider::modifierKeysChanged (const ModifierKeys& modifiers) { if (isEnabled()) @@ -1734,18 +1777,10 @@ private: AccessibleValueRange getRange() const override { return { { slider.getMinimum(), slider.getMaximum() }, - getStepSize() }; + getStepSize (slider) }; } private: - double getStepSize() const - { - auto interval = slider.getInterval(); - - return interval != 0.0 ? interval - : slider.getRange().getLength() * 0.01; - } - Slider& slider; const bool useMaxValue; diff --git a/source/modules/juce_gui_basics/widgets/juce_Slider.h b/source/modules/juce_gui_basics/widgets/juce_Slider.h index 630d5efde..c05cc33ad 100644 --- a/source/modules/juce_gui_basics/widgets/juce_Slider.h +++ b/source/modules/juce_gui_basics/widgets/juce_Slider.h @@ -991,6 +991,8 @@ public: void mouseExit (const MouseEvent&) override; /** @internal */ void mouseEnter (const MouseEvent&) override; + /** @internal */ + bool keyPressed (const KeyPress&) override; //============================================================================== #ifndef DOXYGEN diff --git a/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp b/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp index 9e57b899a..5c2055be2 100644 --- a/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_TextEditor.cpp @@ -868,6 +868,12 @@ struct TextEditor::TextHolderComponent : public Component, TextEditor& owner; +private: + std::unique_ptr createAccessibilityHandler() override + { + return createIgnoredAccessibilityHandler (*this); + } + JUCE_DECLARE_NON_COPYABLE (TextHolderComponent) }; @@ -894,6 +900,11 @@ struct TextEditor::TextEditorViewport : public Viewport } private: + std::unique_ptr createAccessibilityHandler() override + { + return createIgnoredAccessibilityHandler (*this); + } + TextEditor& owner; int lastWordWrapWidth = 0; bool reentrant = false; @@ -933,13 +944,13 @@ TextEditor::TextEditor (const String& name, juce_wchar passwordChar) setWantsKeyboardFocus (true); recreateCaret(); + + juce::Desktop::getInstance().addGlobalMouseListener (this); } TextEditor::~TextEditor() { - if (wasFocused) - if (auto* peer = getPeer()) - peer->dismissPendingTextInput(); + juce::Desktop::getInstance().removeGlobalMouseListener (this); textValue.removeListener (textHolder); textValue.referTo (Value()); @@ -1017,9 +1028,17 @@ void TextEditor::setReadOnly (bool shouldBeReadOnly) readOnly = shouldBeReadOnly; enablementChanged(); invalidateAccessibilityHandler(); + + if (auto* peer = getPeer()) + peer->refreshTextInputTarget(); } } +void TextEditor::setClicksOutsideDismissVirtualKeyboard (bool newValue) +{ + clicksOutsideDismissVirtualKeyboard = newValue; +} + bool TextEditor::isReadOnly() const noexcept { return readOnly || ! isEnabled(); @@ -1027,7 +1046,7 @@ bool TextEditor::isReadOnly() const noexcept bool TextEditor::isTextInputActive() const { - return ! isReadOnly(); + return ! isReadOnly() && (! clicksOutsideDismissVirtualKeyboard || mouseDownInEditor); } void TextEditor::setReturnKeyStartsNewLine (bool shouldStartNewLine) @@ -1322,13 +1341,7 @@ void TextEditor::timerCallbackInt() void TextEditor::checkFocus() { if (! wasFocused && hasKeyboardFocus (false) && ! isCurrentlyBlockedByAnotherModalComponent()) - { wasFocused = true; - - if (auto* peer = getPeer()) - if (! isReadOnly()) - peer->textInputRequired (peer->globalToLocal (getScreenPosition()), *this); - } } void TextEditor::repaintText (Range range) @@ -1827,6 +1840,11 @@ void TextEditor::performPopupMenuAction (const int menuItemID) //============================================================================== void TextEditor::mouseDown (const MouseEvent& e) { + mouseDownInEditor = e.originalComponent == this; + + if (! mouseDownInEditor) + return; + beginDragAutoRepeat (100); newTransaction(); @@ -1865,6 +1883,9 @@ void TextEditor::mouseDown (const MouseEvent& e) void TextEditor::mouseDrag (const MouseEvent& e) { + if (! mouseDownInEditor) + return; + if (wasFocused || ! selectAllTextWhenFocused) if (! (popupMenuEnabled && e.mods.isPopupMenu())) moveCaretTo (getTextIndexAt (e.x, e.y), true); @@ -1872,6 +1893,9 @@ void TextEditor::mouseDrag (const MouseEvent& e) void TextEditor::mouseUp (const MouseEvent& e) { + if (! mouseDownInEditor) + return; + newTransaction(); textHolder->restartTimer(); @@ -1884,6 +1908,9 @@ void TextEditor::mouseUp (const MouseEvent& e) void TextEditor::mouseDoubleClick (const MouseEvent& e) { + if (! mouseDownInEditor) + return; + int tokenEnd = getTextIndexAt (e.x, e.y); int tokenStart = 0; @@ -1950,6 +1977,9 @@ void TextEditor::mouseDoubleClick (const MouseEvent& e) void TextEditor::mouseWheelMove (const MouseEvent& e, const MouseWheelDetails& wheel) { + if (! mouseDownInEditor) + return; + if (! viewport->useMouseWheelMoveIfNeeded (e, wheel)) Component::mouseWheelMove (e, wheel); } @@ -2214,9 +2244,6 @@ void TextEditor::focusLost (FocusChangeType) underlinedSections.clear(); - if (auto* peer = getPeer()) - peer->dismissPendingTextInput(); - updateCaretPosition(); postCommandMessage (TextEditorDefs::focusLossMessageId); @@ -2668,10 +2695,10 @@ void TextEditor::coalesceSimilarSections() } //============================================================================== -class TextEditorAccessibilityHandler : public AccessibilityHandler +class TextEditor::EditorAccessibilityHandler : public AccessibilityHandler { public: - explicit TextEditorAccessibilityHandler (TextEditor& textEditorToWrap) + explicit EditorAccessibilityHandler (TextEditor& textEditorToWrap) : AccessibilityHandler (textEditorToWrap, textEditorToWrap.isReadOnly() ? AccessibilityRole::staticText : AccessibilityRole::editableText, {}, @@ -2699,10 +2726,20 @@ private: void setSelection (Range r) override { + if (r == textEditor.getHighlightedRegion()) + return; + if (r.isEmpty()) + { textEditor.setCaretPosition (r.getStart()); + } else - textEditor.setHighlightedRegion (r); + { + const auto cursorAtStart = r.getEnd() == textEditor.getHighlightedRegion().getStart() + || r.getEnd() == textEditor.getHighlightedRegion().getEnd(); + textEditor.moveCaretTo (cursorAtStart ? r.getEnd() : r.getStart(), false); + textEditor.moveCaretTo (cursorAtStart ? r.getStart() : r.getEnd(), true); + } } String getText (Range r) const override @@ -2748,12 +2785,12 @@ private: TextEditor& textEditor; //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextEditorAccessibilityHandler) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EditorAccessibilityHandler) }; std::unique_ptr TextEditor::createAccessibilityHandler() { - return std::make_unique (*this); + return std::make_unique (*this); } } // namespace juce diff --git a/source/modules/juce_gui_basics/widgets/juce_TextEditor.h b/source/modules/juce_gui_basics/widgets/juce_TextEditor.h index 8b1fbc25a..d817e5ec0 100644 --- a/source/modules/juce_gui_basics/widgets/juce_TextEditor.h +++ b/source/modules/juce_gui_basics/widgets/juce_TextEditor.h @@ -665,8 +665,24 @@ public: void setInputRestrictions (int maxTextLength, const String& allowedCharacters = String()); + /** Sets the type of virtual keyboard that should be displayed when this editor has + focus. + */ void setKeyboardType (VirtualKeyboardType type) noexcept { keyboardType = type; } + /** Sets the behaviour of mouse/touch interactions outside this component. + + If true, then presses outside of the TextEditor will dismiss the virtual keyboard. + If false, then the virtual keyboard will remain onscreen for as long as the TextEditor has + keyboard focus. + */ + void setClicksOutsideDismissVirtualKeyboard (bool); + + /** Returns true if the editor is configured to hide the virtual keyboard when the mouse is + pressed on another component. + */ + bool getClicksOutsideDismissVirtualKeyboard() const { return clicksOutsideDismissVirtualKeyboard; } + //============================================================================== /** This abstract base class is implemented by LookAndFeel classes to provide TextEditor drawing functionality. @@ -744,6 +760,7 @@ private: struct TextEditorViewport; struct InsertAction; struct RemoveAction; + class EditorAccessibilityHandler; std::unique_ptr viewport; TextHolderComponent* textHolder; @@ -765,6 +782,8 @@ private: bool valueTextNeedsUpdating = false; bool consumeEscAndReturnKeys = true; bool underlineWhitespace = true; + bool mouseDownInEditor = false; + bool clicksOutsideDismissVirtualKeyboard = false; UndoManager undoManager; std::unique_ptr caret; diff --git a/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp b/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp index 7b6290fac..9c921740a 100644 --- a/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_ToolbarItemComponent.cpp @@ -247,7 +247,7 @@ std::unique_ptr ToolbarItemComponent::createAccessibilityH && itemId != ToolbarItemFactory::flexibleSpacerId); if (! shouldItemBeAccessible) - return nullptr; + return createIgnoredAccessibilityHandler (*this); return std::make_unique (*this, AccessibilityRole::button); } diff --git a/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp b/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp index e5941ae43..c040a67de 100644 --- a/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp +++ b/source/modules/juce_gui_basics/widgets/juce_TreeView.cpp @@ -233,7 +233,7 @@ private: std::unique_ptr createAccessibilityHandler() override { if (hasCustomComponent() && customComponent->getAccessibilityHandler() != nullptr) - return nullptr; + return createIgnoredAccessibilityHandler (*this); return std::make_unique (*this); } diff --git a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp index 9a4dc009a..3ab01734a 100644 --- a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp +++ b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.cpp @@ -34,12 +34,15 @@ ComponentPeer::ComponentPeer (Component& comp, int flags) styleFlags (flags), uniqueID (lastUniquePeerID += 2) // increment by 2 so that this can never hit 0 { - Desktop::getInstance().peers.add (this); + auto& desktop = Desktop::getInstance(); + desktop.peers.add (this); + desktop.addFocusChangeListener (this); } ComponentPeer::~ComponentPeer() { auto& desktop = Desktop::getInstance(); + desktop.removeFocusChangeListener (this); desktop.peers.removeFirstMatchingValue (this); desktop.triggerFocusCallback(); } @@ -262,6 +265,19 @@ void ComponentPeer::handleModifierKeysChange() target->internalModifierKeysChanged(); } +void ComponentPeer::refreshTextInputTarget() +{ + const auto* lastTarget = std::exchange (textInputTarget, findCurrentTextInputTarget()); + + if (lastTarget == textInputTarget) + return; + + if (textInputTarget == nullptr) + dismissPendingTextInput(); + else if (auto* c = Component::getCurrentlyFocusedComponent()) + textInputRequired (globalToLocal (c->getScreenPosition()), *textInputTarget); +} + TextInputTarget* ComponentPeer::findCurrentTextInputTarget() { auto* c = Component::getCurrentlyFocusedComponent(); @@ -591,4 +607,9 @@ void ComponentPeer::forceDisplayUpdate() Desktop::getInstance().displays->refresh(); } +void ComponentPeer::globalFocusChanged (Component*) +{ + refreshTextInputTarget(); +} + } // namespace juce diff --git a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h index 3c0640c3a..64084b9d8 100644 --- a/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/source/modules/juce_gui_basics/windows/juce_ComponentPeer.h @@ -40,7 +40,7 @@ namespace juce @tags{GUI} */ -class JUCE_API ComponentPeer +class JUCE_API ComponentPeer : private FocusChangeListener { public: //============================================================================== @@ -135,7 +135,7 @@ public: ComponentPeer (Component& component, int styleFlags); /** Destructor. */ - virtual ~ComponentPeer(); + ~ComponentPeer() override; //============================================================================== /** Returns the component being represented by this peer. */ @@ -356,25 +356,19 @@ public: /** Called whenever a modifier key is pressed or released. */ void handleModifierKeysChange(); - //============================================================================== - /** Tells the window that text input may be required at the given position. - This may cause things like a virtual on-screen keyboard to appear, depending - on the OS. - */ - virtual void textInputRequired (Point position, TextInputTarget&) = 0; - /** If there's a currently active input-method context - i.e. characters are being composed using multiple keystrokes - this should commit the current state of the - context to the text and clear the context. + context to the text and clear the context. This should not hide the virtual keyboard. */ virtual void closeInputMethodContext(); - /** If there's some kind of OS input-method in progress, this should dismiss it. + /** Alerts the peer that the current text input target has changed somehow. - Overrides of this function should call closeInputMethodContext(). + The peer may hide or show the virtual keyboard as a result of this call. */ - virtual void dismissPendingTextInput(); + void refreshTextInputTarget(); + //============================================================================== /** Returns the currently focused TextInputTarget, or null if none is found. */ TextInputTarget* findCurrentTextInputTarget(); @@ -536,10 +530,30 @@ private: //============================================================================== virtual void appStyleChanged() {} + /** Tells the window that text input may be required at the given position. + This may cause things like a virtual on-screen keyboard to appear, depending + on the OS. + + This function should not be called directly by Components - use refreshTextInputTarget + instead. + */ + virtual void textInputRequired (Point, TextInputTarget&) = 0; + + /** If there's some kind of OS input-method in progress, this should dismiss it. + + Overrides of this function should call closeInputMethodContext(). + + This function should not be called directly by Components - use refreshTextInputTarget + instead. + */ + virtual void dismissPendingTextInput(); + + void globalFocusChanged (Component*) override; Component* getTargetForKeyPress(); WeakReference lastFocusedComponent, dragAndDropTargetComponent; Component* lastDragAndDropCompUnderMouse = nullptr; + TextInputTarget* textInputTarget = nullptr; const uint32 uniqueID; bool isWindowMinimised = false; diff --git a/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp b/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp index f903d908d..95db8b9c7 100644 --- a/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp +++ b/source/modules/juce_gui_extra/code_editor/juce_CodeEditorComponent.cpp @@ -71,6 +71,9 @@ private: void setSelection (Range r) override { + if (r == codeEditorComponent.getHighlightedRegion()) + return; + if (r.isEmpty()) { codeEditorComponent.caretPos.setPosition (r.getStart()); @@ -79,8 +82,10 @@ private: auto& doc = codeEditorComponent.document; - codeEditorComponent.selectRegion (CodeDocument::Position (doc, r.getStart()), - CodeDocument::Position (doc, r.getEnd())); + const auto cursorAtStart = r.getEnd() == codeEditorComponent.getHighlightedRegion().getStart() + || r.getEnd() == codeEditorComponent.getHighlightedRegion().getEnd(); + codeEditorComponent.selectRegion (CodeDocument::Position (doc, cursorAtStart ? r.getEnd() : r.getStart()), + CodeDocument::Position (doc, cursorAtStart ? r.getStart() : r.getEnd())); } String getText (Range r) const override @@ -126,7 +131,7 @@ private: localRects.add (startPos.x, startPos.y, - endPos.x - startPos.x, + jmax (1, endPos.x - startPos.x), codeEditorComponent.getLineHeight()); } diff --git a/source/modules/juce_gui_extra/juce_gui_extra.cpp b/source/modules/juce_gui_extra/juce_gui_extra.cpp index fe028fa68..dbb4d75a5 100644 --- a/source/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/source/modules/juce_gui_extra/juce_gui_extra.cpp @@ -122,22 +122,22 @@ #endif //============================================================================== -// #include "documents/juce_FileBasedDocument.cpp" -// #include "code_editor/juce_CodeDocument.cpp" -// #include "code_editor/juce_CodeEditorComponent.cpp" -// #include "code_editor/juce_CPlusPlusCodeTokeniser.cpp" -// #include "code_editor/juce_XMLCodeTokeniser.cpp" -// #include "code_editor/juce_LuaCodeTokeniser.cpp" -// #include "misc/juce_BubbleMessageComponent.cpp" -// #include "misc/juce_ColourSelector.cpp" -// #include "misc/juce_KeyMappingEditorComponent.cpp" -// #include "misc/juce_PreferencesPanel.cpp" -// #include "misc/juce_PushNotifications.cpp" -// #include "misc/juce_RecentlyOpenedFilesList.cpp" -// #include "misc/juce_SplashScreen.cpp" -// #include "misc/juce_SystemTrayIconComponent.cpp" -// #include "misc/juce_LiveConstantEditor.cpp" -// #include "misc/juce_AnimatedAppComponent.cpp" +#include "documents/juce_FileBasedDocument.cpp" +#include "code_editor/juce_CodeDocument.cpp" +#include "code_editor/juce_CodeEditorComponent.cpp" +#include "code_editor/juce_CPlusPlusCodeTokeniser.cpp" +#include "code_editor/juce_XMLCodeTokeniser.cpp" +#include "code_editor/juce_LuaCodeTokeniser.cpp" +#include "misc/juce_BubbleMessageComponent.cpp" +#include "misc/juce_ColourSelector.cpp" +#include "misc/juce_KeyMappingEditorComponent.cpp" +#include "misc/juce_PreferencesPanel.cpp" +#include "misc/juce_PushNotifications.cpp" +#include "misc/juce_RecentlyOpenedFilesList.cpp" +#include "misc/juce_SplashScreen.cpp" +#include "misc/juce_SystemTrayIconComponent.cpp" +#include "misc/juce_LiveConstantEditor.cpp" +#include "misc/juce_AnimatedAppComponent.cpp" //============================================================================== #if JUCE_MAC || JUCE_IOS diff --git a/source/modules/juce_gui_extra/juce_gui_extra.h b/source/modules/juce_gui_extra/juce_gui_extra.h index ebed0f5cc..506b2a02d 100644 --- a/source/modules/juce_gui_extra/juce_gui_extra.h +++ b/source/modules/juce_gui_extra/juce_gui_extra.h @@ -35,7 +35,7 @@ ID: juce_gui_extra vendor: juce - version: 6.1.6 + version: 7.0.1 name: JUCE extended GUI classes description: Miscellaneous GUI classes for specialised tasks. website: http://www.juce.com/juce diff --git a/source/modules/juce_gui_extra/misc/juce_ColourSelector.cpp b/source/modules/juce_gui_extra/misc/juce_ColourSelector.cpp index 07b1a5d54..91d014335 100644 --- a/source/modules/juce_gui_extra/misc/juce_ColourSelector.cpp +++ b/source/modules/juce_gui_extra/misc/juce_ColourSelector.cpp @@ -411,12 +411,8 @@ ColourSelector::ColourSelector (int sectionsToShow, int edge, int gapAroundColou sliders[3]->setVisible ((flags & showAlphaChannel) != 0); - // VS2015 needs some scoping braces around this if statement to - // avoid a compiler bug. for (auto& slider : sliders) - { slider->onValueChange = [this] { changeColour(); }; - } } if ((flags & showColourspace) != 0) diff --git a/source/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp b/source/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp deleted file mode 100644 index 87b461c05..000000000 --- a/source/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp +++ /dev/null @@ -1,1648 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -#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") \ - METHOD (setBypassDnd, "setBypassDnd", "(Z)V") \ - METHOD (setDescription, "setDescription", "(Ljava/lang/String;)V") \ - METHOD (setGroup, "setGroup", "(Ljava/lang/String;)V") \ - METHOD (setImportance, "setImportance", "(I)V") \ - METHOD (setLightColor, "setLightColor", "(I)V") \ - METHOD (setLockscreenVisibility, "setLockscreenVisibility", "(I)V") \ - METHOD (setShowBadge, "setShowBadge", "(Z)V") \ - METHOD (setSound, "setSound", "(Landroid/net/Uri;Landroid/media/AudioAttributes;)V") \ - METHOD (setVibrationPattern, "setVibrationPattern", "([J)V") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannel, "android/app/NotificationChannel", 26) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/CharSequence;)V") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationChannelGroup, "android/app/NotificationChannelGroup", 26) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - FIELD (extras, "extras", "Landroid/os/Bundle;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidNotification, "android/app/Notification", 19) -#undef JNI_CLASS_MEMBERS - -#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_WITH_MIN_SDK (NotificationActionBuilder, "android/app/Notification$Action$Builder", 20) -#undef JNI_CLASS_MEMBERS - -#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;") \ - METHOD (setContentIntent, "setContentIntent", "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \ - METHOD (setContentText, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ - METHOD (setContentTitle, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ - METHOD (setDefaults, "setDefaults", "(I)Landroid/app/Notification$Builder;") \ - METHOD (setDeleteIntent, "setDeleteIntent", "(Landroid/app/PendingIntent;)Landroid/app/Notification$Builder;") \ - METHOD (setLargeIcon, "setLargeIcon", "(Landroid/graphics/Bitmap;)Landroid/app/Notification$Builder;") \ - METHOD (setLights, "setLights", "(III)Landroid/app/Notification$Builder;") \ - METHOD (setNumber, "setNumber", "(I)Landroid/app/Notification$Builder;") \ - METHOD (setOngoing, "setOngoing", "(Z)Landroid/app/Notification$Builder;") \ - METHOD (setOnlyAlertOnce, "setOnlyAlertOnce", "(Z)Landroid/app/Notification$Builder;") \ - METHOD (setProgress, "setProgress", "(IIZ)Landroid/app/Notification$Builder;") \ - METHOD (setSmallIcon, "setSmallIcon", "(I)Landroid/app/Notification$Builder;") \ - METHOD (setSound, "setSound", "(Landroid/net/Uri;)Landroid/app/Notification$Builder;") \ - METHOD (setTicker, "setTicker", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;") \ - METHOD (setVibrate, "setVibrate", "([J)Landroid/app/Notification$Builder;") \ - METHOD (setWhen, "setWhen", "(J)Landroid/app/Notification$Builder;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderBase, "android/app/Notification$Builder", 11) -#undef JNI_CLASS_MEMBERS - -#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_WITH_MIN_SDK (NotificationBuilderApi16, "android/app/Notification$Builder", 16) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (setShowWhen, "setShowWhen", "(Z)Landroid/app/Notification$Builder;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi17, "android/app/Notification$Builder", 17) -#undef JNI_CLASS_MEMBERS - -#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;") \ - METHOD (setGroup, "setGroup", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") \ - METHOD (setGroupSummary, "setGroupSummary", "(Z)Landroid/app/Notification$Builder;") \ - METHOD (setSortKey, "setSortKey", "(Ljava/lang/String;)Landroid/app/Notification$Builder;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi20, "android/app/Notification$Builder", 20) -#undef JNI_CLASS_MEMBERS - -#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_WITH_MIN_SDK (NotificationBuilderApi21, "android/app/Notification$Builder", 21) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (setChronometerCountDown, "setChronometerCountDown", "(Z)Landroid/app/Notification$Builder;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationBuilderApi24, "android/app/Notification$Builder", 24) -#undef JNI_CLASS_MEMBERS - -#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_WITH_MIN_SDK (NotificationBuilderApi26, "android/app/Notification$Builder", 26) -#undef JNI_CLASS_MEMBERS - -#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") - -DECLARE_JNI_CLASS (NotificationManagerBase, "android/app/NotificationManager") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getActiveNotifications, "getActiveNotifications", "()[Landroid/service/notification/StatusBarNotification;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi23, "android/app/NotificationManager", 23) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (areNotificationsEnabled, "areNotificationsEnabled", "()Z") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (NotificationManagerApi24, "android/app/NotificationManager", 24) -#undef JNI_CLASS_MEMBERS - -#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_WITH_MIN_SDK (NotificationManagerApi26, "android/app/NotificationManager", 26) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getResultsFromIntent, "getResultsFromIntent", "(Landroid/content/Intent;)Landroid/os/Bundle;") - -DECLARE_JNI_CLASS_WITH_MIN_SDK (RemoteInput, "android/app/RemoteInput", 20) -#undef JNI_CLASS_MEMBERS - -#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_WITH_MIN_SDK (RemoteInputBuilder, "android/app/RemoteInput$Builder", 20) -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getNotification, "getNotification", "()Landroid/app/Notification;") - - DECLARE_JNI_CLASS_WITH_MIN_SDK (StatusBarNotification, "android/service/notification/StatusBarNotification", 23) - #undef JNI_CLASS_MEMBERS - -//============================================================================== -#if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/iid/FirebaseInstanceId;") \ - METHOD (getToken, "getToken", "()Ljava/lang/String;") - - DECLARE_JNI_CLASS (FirebaseInstanceId, "com/google/firebase/iid/FirebaseInstanceId") - #undef JNI_CLASS_MEMBERS -#endif - -#if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - STATICMETHOD (getInstance, "getInstance", "()Lcom/google/firebase/messaging/FirebaseMessaging;") \ - METHOD (send, "send", "(Lcom/google/firebase/messaging/RemoteMessage;)V") \ - METHOD (subscribeToTopic, "subscribeToTopic", "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \ - METHOD (unsubscribeFromTopic, "unsubscribeFromTopic", "(Ljava/lang/String;)Lcom/google/android/gms/tasks/Task;") \ - - DECLARE_JNI_CLASS (FirebaseMessaging, "com/google/firebase/messaging/FirebaseMessaging") - #undef JNI_CLASS_MEMBERS - - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getCollapseKey, "getCollapseKey", "()Ljava/lang/String;") \ - METHOD (getData, "getData", "()Ljava/util/Map;") \ - METHOD (getFrom, "getFrom", "()Ljava/lang/String;") \ - METHOD (getMessageId, "getMessageId", "()Ljava/lang/String;") \ - METHOD (getMessageType, "getMessageType", "()Ljava/lang/String;") \ - METHOD (getNotification, "getNotification", "()Lcom/google/firebase/messaging/RemoteMessage$Notification;") \ - METHOD (getSentTime, "getSentTime", "()J") \ - METHOD (getTo, "getTo", "()Ljava/lang/String;") \ - METHOD (getTtl, "getTtl", "()I") - - DECLARE_JNI_CLASS (RemoteMessage, "com/google/firebase/messaging/RemoteMessage") - #undef JNI_CLASS_MEMBERS - - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (addData, "addData", "(Ljava/lang/String;Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ - METHOD (build, "build", "()Lcom/google/firebase/messaging/RemoteMessage;") \ - METHOD (constructor, "", "(Ljava/lang/String;)V") \ - METHOD (setCollapseKey, "setCollapseKey", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ - METHOD (setMessageId, "setMessageId", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ - METHOD (setMessageType, "setMessageType", "(Ljava/lang/String;)Lcom/google/firebase/messaging/RemoteMessage$Builder;") \ - METHOD (setTtl, "setTtl", "(I)Lcom/google/firebase/messaging/RemoteMessage$Builder;") - - DECLARE_JNI_CLASS (RemoteMessageBuilder, "com/google/firebase/messaging/RemoteMessage$Builder") - #undef JNI_CLASS_MEMBERS - - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getBody, "getBody", "()Ljava/lang/String;") \ - METHOD (getBodyLocalizationArgs, "getBodyLocalizationArgs", "()[Ljava/lang/String;") \ - METHOD (getBodyLocalizationKey, "getBodyLocalizationKey", "()Ljava/lang/String;") \ - METHOD (getClickAction, "getClickAction", "()Ljava/lang/String;") \ - METHOD (getColor, "getColor", "()Ljava/lang/String;") \ - METHOD (getIcon, "getIcon", "()Ljava/lang/String;") \ - METHOD (getLink, "getLink", "()Landroid/net/Uri;") \ - METHOD (getSound, "getSound", "()Ljava/lang/String;") \ - METHOD (getTag, "getTag", "()Ljava/lang/String;") \ - METHOD (getTitle, "getTitle", "()Ljava/lang/String;") \ - METHOD (getTitleLocalizationArgs, "getTitleLocalizationArgs", "()[Ljava/lang/String;") \ - METHOD (getTitleLocalizationKey, "getTitleLocalizationKey", "()Ljava/lang/String;") - - DECLARE_JNI_CLASS (RemoteMessageNotification, "com/google/firebase/messaging/RemoteMessage$Notification") - #undef JNI_CLASS_MEMBERS -#endif - -//============================================================================== -bool PushNotifications::Notification::isValid() const noexcept -{ - bool isValidForPreApi26 = title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && icon.isNotEmpty(); - bool apiAtLeast26 = (getAndroidSDKVersion() >= 26); - - if (apiAtLeast26) - return isValidForPreApi26 && channelId.isNotEmpty(); - - return isValidForPreApi26; -} - -//============================================================================== -struct PushNotifications::Pimpl -{ - Pimpl (PushNotifications& p) - : owner (p) - {} - - bool areNotificationsEnabled() const - { - if (getAndroidSDKVersion() >= 24) - { - auto* env = getEnv(); - - auto notificationManager = getNotificationManager(); - - if (notificationManager.get() != nullptr) - return env->CallBooleanMethod (notificationManager, NotificationManagerApi24.areNotificationsEnabled); - } - - return true; - } - - //============================================================================== - void sendLocalNotification (const PushNotifications::Notification& n) - { - // All required fields have to be setup! - jassert (n.isValid()); - - auto* env = getEnv(); - - auto notificationManager = getNotificationManager(); - - if (notificationManager.get() != nullptr) - { - auto notification = juceNotificationToJavaNotification (n); - - auto tag = javaString (n.identifier); - const int id = 0; - - env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.notify, tag.get(), id, notification.get()); - } - } - - void getDeliveredNotifications() const - { - if (getAndroidSDKVersion() >= 23) - { - auto* env = getEnv(); - - Array notifications; - - auto notificationManager = getNotificationManager(); - jassert (notificationManager != nullptr); - - if (notificationManager.get() != nullptr) - { - auto statusBarNotifications = LocalRef ((jobjectArray)env->CallObjectMethod (notificationManager, - NotificationManagerApi23.getActiveNotifications)); - - 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)); - - 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 ({}); }); - } - } - - void notifyListenersAboutLocalNotification (const LocalRef& intent) - { - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - auto bundle = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); - - const auto notification = localNotificationBundleToJuceNotification (bundle); - - auto packageName = juceString ((jstring) env->CallObjectMethod (context.get(), AndroidContext.getPackageName)); - - String notificationString = packageName + ".JUCE_NOTIFICATION."; - String notificationButtonActionString = packageName + ".JUCE_NOTIFICATION_BUTTON_ACTION."; - String notificationTextInputActionString = packageName + ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION."; - - auto actionString = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction)); - - if (actionString.contains (notificationString)) - { - owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, notification); }); - } - else if (actionString.contains (notificationButtonActionString)) - { - auto prefix = notificationButtonActionString + notification.identifier + "."; - - auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false) // skip prefix - .fromFirstOccurrenceOf (".", false, false); // skip action index - - owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, {}); }); - } - else if (getAndroidSDKVersion() >= 20 && actionString.contains (notificationTextInputActionString)) - { - auto prefix = notificationTextInputActionString + notification.identifier + "."; - - auto actionTitle = actionString.fromLastOccurrenceOf (prefix, false, false) // skip prefix - .fromFirstOccurrenceOf (".", false, false); // skip action index - - auto actionIndex = actionString.fromLastOccurrenceOf (prefix, false, false).upToFirstOccurrenceOf (".", false, false); - auto resultKeyString = javaString (actionTitle + actionIndex); - - auto remoteInputResult = LocalRef (env->CallStaticObjectMethod (RemoteInput, RemoteInput.getResultsFromIntent, intent.get())); - String responseString; - - if (remoteInputResult.get() == nullptr) - { - auto charSequence = LocalRef (env->CallObjectMethod (remoteInputResult, AndroidBundle.getCharSequence, resultKeyString.get())); - auto responseStringRef = LocalRef ((jstring) env->CallObjectMethod (charSequence, JavaCharSequence.toString)); - responseString = juceString (responseStringRef.get()); - } - - owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, notification, actionTitle, responseString); }); - } - } - - void notifyListenersAboutLocalNotificationDeleted (const LocalRef& intent) - { - auto* env = getEnv(); - - auto bundle = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); - auto notification = localNotificationBundleToJuceNotification (bundle); - - owner.listeners.call ([&] (Listener& l) { l.localNotificationDismissedByUser (notification); }); - } - - void removeAllDeliveredNotifications() - { - auto* env = getEnv(); - - auto notificationManager = getNotificationManager(); - - if (notificationManager.get() != nullptr) - env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancelAll); - } - - void removeDeliveredNotification (const String& identifier) - { - auto* env = getEnv(); - - auto notificationManager = getNotificationManager(); - - if (notificationManager.get() != nullptr) - { - auto tag = javaString (identifier); - const int id = 0; - - env->CallVoidMethod (notificationManager.get(), NotificationManagerBase.cancel, tag.get(), id); - } - } - - //============================================================================== - String getDeviceToken() const - { - #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) - auto* env = getEnv(); - - auto instanceId = LocalRef (env->CallStaticObjectMethod (FirebaseInstanceId, FirebaseInstanceId.getInstance)); - - return juceString ((jstring) env->CallObjectMethod (instanceId, FirebaseInstanceId.getToken)); - #else - return {}; - #endif - } - - void notifyListenersTokenRefreshed (const String& token) - { - #if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) - MessageManager::callAsync ([this, token] - { - owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (token); }); - }); - #else - ignoreUnused (token); - #endif - } - - //============================================================================== - void subscribeToTopic (const String& topic) - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - auto* env = getEnv(); - - auto firebaseMessaging = LocalRef (env->CallStaticObjectMethod (FirebaseMessaging, - FirebaseMessaging.getInstance)); - - env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.subscribeToTopic, javaString (topic).get()); - #else - ignoreUnused (topic); - #endif - } - - void unsubscribeFromTopic (const String& topic) - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - auto* env = getEnv(); - - auto firebaseMessaging = LocalRef (env->CallStaticObjectMethod (FirebaseMessaging, - FirebaseMessaging.getInstance)); - - env->CallObjectMethod (firebaseMessaging, FirebaseMessaging.unsubscribeFromTopic, javaString (topic).get()); - #else - ignoreUnused (topic); - #endif - } - - void sendUpstreamMessage (const String& serverSenderId, - const String& collapseKey, - const String& messageId, - const String& messageType, - int timeToLive, - const StringPairArray& additionalData) - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - auto* env = getEnv(); - - auto messageBuilder = LocalRef (env->NewObject (RemoteMessageBuilder, - RemoteMessageBuilder.constructor, - javaString (serverSenderId + "@gcm_googleapis.com").get())); - - env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setCollapseKey, javaString (collapseKey).get()); - env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageId, javaString (messageId).get()); - env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setMessageType, javaString (messageType).get()); - env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.setTtl, timeToLive); - - auto keys = additionalData.getAllKeys(); - - for (const auto& key : keys) - env->CallObjectMethod (messageBuilder, - RemoteMessageBuilder.addData, - javaString (key).get(), - javaString (additionalData[key]).get()); - - auto message = LocalRef (env->CallObjectMethod (messageBuilder, RemoteMessageBuilder.build)); - - auto firebaseMessaging = LocalRef (env->CallStaticObjectMethod (FirebaseMessaging, - FirebaseMessaging.getInstance)); - - env->CallVoidMethod (firebaseMessaging, FirebaseMessaging.send, message.get()); - #else - ignoreUnused (serverSenderId, collapseKey, messageId, messageType); - ignoreUnused (timeToLive, additionalData); - #endif - } - - void notifyListenersAboutRemoteNotificationFromSystemTray (const LocalRef& intent) - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - auto* env = getEnv(); - - auto bundle = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); - auto notification = remoteNotificationBundleToJuceNotification (bundle); - - owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); }); - #else - ignoreUnused (intent); - #endif - } - - void notifyListenersAboutRemoteNotificationFromService (const LocalRef& remoteNotification) - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - GlobalRef rn (remoteNotification); - - MessageManager::callAsync ([this, rn] - { - auto notification = firebaseRemoteNotificationToJuceNotification (rn.get()); - owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, notification); }); - }); - #else - ignoreUnused (remoteNotification); - #endif - } - - void notifyListenersAboutRemoteNotificationsDeleted() - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - MessageManager::callAsync ([this] - { - owner.listeners.call ([] (Listener& l) { l.remoteNotificationsDeleted(); }); - }); - #endif - } - - void notifyListenersAboutUpstreamMessageSent (const LocalRef& messageId) - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - GlobalRef mid (LocalRef(messageId.get())); - - MessageManager::callAsync ([this, mid] - { - auto midString = juceString ((jstring) mid.get()); - owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSent (midString); }); - }); - #else - ignoreUnused (messageId); - #endif - } - - void notifyListenersAboutUpstreamMessageSendingError (const LocalRef& messageId, - const LocalRef& error) - { - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - GlobalRef mid (LocalRef(messageId.get())), e (LocalRef(error.get())); - - MessageManager::callAsync ([this, mid, e] - { - auto midString = juceString ((jstring) mid.get()); - auto eString = juceString ((jstring) e.get()); - - owner.listeners.call ([&] (Listener& l) { l.upstreamMessageSendingError (midString, eString); }); - }); - #else - ignoreUnused (messageId, error); - #endif - } - - static LocalRef getNotificationManager() - { - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - return LocalRef (env->CallObjectMethod (context.get(), - AndroidContext.getSystemService, - javaString ("notification").get())); - } - - static LocalRef juceNotificationToJavaNotification (const PushNotifications::Notification& n) - { - auto* env = getEnv(); - - auto notificationBuilder = createNotificationBuilder (n); - - setupRequiredFields (n, notificationBuilder); - setupOptionalFields (n, notificationBuilder); - - if (n.actions.size() > 0) - setupActions (n, notificationBuilder); - - if (getAndroidSDKVersion() >= 16) - return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.build)); - - return LocalRef (env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.getNotification)); - } - - static LocalRef createNotificationBuilder (const PushNotifications::Notification& n) - { - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - jclass builderClass = env->FindClass ("android/app/Notification$Builder"); - jassert (builderClass != nullptr); - - if (builderClass == nullptr) - return LocalRef (nullptr); - - jmethodID builderConstructor = nullptr; - - const bool apiAtLeast26 = (getAndroidSDKVersion() >= 26); - - if (apiAtLeast26) - builderConstructor = env->GetMethodID (builderClass, "", "(Landroid/content/Context;Ljava/lang/String;)V"); - else - builderConstructor = env->GetMethodID (builderClass, "", "(Landroid/content/Context;)V"); - - jassert (builderConstructor != nullptr); - - if (builderConstructor == nullptr) - return LocalRef (nullptr); - - if (apiAtLeast26) - return LocalRef (env->NewObject (builderClass, builderConstructor, - context.get(), javaString (n.channelId).get())); - - return LocalRef (env->NewObject (builderClass, builderConstructor, context.get())); - } - - static void setupRequiredFields (const PushNotifications::Notification& n, LocalRef& notificationBuilder) - { - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - 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) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); - auto actionStringSuffix = javaString (".JUCE_NOTIFICATION." + n.identifier); - auto actionString = LocalRef ((jstring)env->CallObjectMethod (packageNameString, JavaString.concat, actionStringSuffix.get())); - - env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get()); - // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on - env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get()); - - auto notifyPendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, - AndroidPendingIntent.getActivity, - context.get(), - 1002, - notifyIntent.get(), - 0)); - - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentTitle, javaString (n.title).get()); - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentText, javaString (n.body).get()); - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentIntent, notifyPendingIntent.get()); - - 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 (getAndroidSDKVersion() >= 21 && n.publicVersion != nullptr) - { - // Public version of a notification is not expected to have another public one! - jassert (n.publicVersion->publicVersion == nullptr); - - auto publicNotificationBuilder = createNotificationBuilder (n); - - setupRequiredFields (*n.publicVersion, publicNotificationBuilder); - setupOptionalFields (*n.publicVersion, publicNotificationBuilder); - - auto publicVersion = LocalRef (env->CallObjectMethod (publicNotificationBuilder, NotificationBuilderApi16.build)); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setPublicVersion, publicVersion.get()); - } - } - - static LocalRef juceNotificationToBundle (const PushNotifications::Notification& n) - { - auto* env = getEnv(); - - auto bundle = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); - - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("identifier") .get(), javaString (n.identifier).get()); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("title") .get(), javaString (n.title).get()); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("body") .get(), javaString (n.body).get()); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("subtitle") .get(), javaString (n.subtitle).get()); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("badgeNumber") .get(), n.badgeNumber); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("soundToPlay") .get(), javaString (n.soundToPlay.toString (true)).get()); - env->CallVoidMethod (bundle, AndroidBundle.putBundle, javaString ("properties") .get(), varToBundleWithPropertiesString (n.properties).get()); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("icon") .get(), javaString (n.icon).get()); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("channelId") .get(), javaString (n.channelId).get()); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("tickerText") .get(), javaString (n.tickerText).get()); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("progressMax") .get(), n.progress.max); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("progressCurrent") .get(), n.progress.current); - env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("progressIndeterminate") .get(), n.progress.indeterminate); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("person") .get(), javaString (n.person).get()); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("type") .get(), n.type); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("priority") .get(), n.priority); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("lockScreenAppearance") .get(), n.lockScreenAppearance); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("groupId") .get(), javaString (n.groupId).get()); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("groupSortKey") .get(), javaString (n.groupSortKey).get()); - env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("groupSummary") .get(), n.groupSummary); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("accentColour") .get(), n.accentColour.getARGB()); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledColour") .get(), n.ledColour.getARGB()); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledBlinkPatternMsToBeOn") .get(), n.ledBlinkPattern.msToBeOn); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("ledBlinkPatternMsToBeOff").get(), n.ledBlinkPattern.msToBeOff); - env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("shouldAutoCancel") .get(), n.shouldAutoCancel); - env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("localOnly") .get(), n.localOnly); - env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("ongoing") .get(), n.ongoing); - env->CallVoidMethod (bundle, AndroidBundle.putBoolean, javaString ("alertOnlyOnce") .get(), n.alertOnlyOnce); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("timestampVisibility") .get(), n.timestampVisibility); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("badgeIconType") .get(), n.badgeIconType); - env->CallVoidMethod (bundle, AndroidBundle.putInt, javaString ("groupAlertBehaviour") .get(), n.groupAlertBehaviour); - env->CallVoidMethod (bundle, AndroidBundle.putLong, javaString ("timeoutAfterMs") .get(), (jlong)n.timeoutAfterMs); - - const int size = n.vibrationPattern.size(); - - if (size > 0) - { - auto array = LocalRef (env->NewLongArray (size)); - - jlong* elements = env->GetLongArrayElements (array, nullptr); - - for (int i = 0; i < size; ++i) - elements[i] = (jlong) n.vibrationPattern[i]; - - env->SetLongArrayRegion (array, 0, size, elements); - env->CallVoidMethod (bundle, AndroidBundle.putLongArray, javaString ("vibrationPattern").get(), array.get()); - } - - return bundle; - } - - static void setupOptionalFields (const PushNotifications::Notification& n, LocalRef& notificationBuilder) - { - auto* env = getEnv(); - - if (n.subtitle.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setContentInfo, javaString (n.subtitle).get()); - - auto soundName = n.soundToPlay.toString (true); - - if (soundName == "default_os_sound") - { - const int playDefaultSound = 1; - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDefaults, playDefaultSound); - } - else if (! soundName.isEmpty()) - { - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setSound, juceUrlToAndroidUri (n.soundToPlay).get()); - } - - if (n.largeIcon.isValid()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setLargeIcon, imagetoJavaBitmap (n.largeIcon).get()); - - if (n.tickerText.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setTicker, javaString (n.tickerText).get()); - - if (n.ledColour != Colour()) - { - env->CallObjectMethod (notificationBuilder, - NotificationBuilderBase.setLights, - n.ledColour.getARGB(), - n.ledBlinkPattern.msToBeOn, - n.ledBlinkPattern.msToBeOff); - } - - if (! n.vibrationPattern.isEmpty()) - { - const int size = n.vibrationPattern.size(); - - if (size > 0) - { - auto array = LocalRef (env->NewLongArray (size)); - - jlong* elements = env->GetLongArrayElements (array, nullptr); - - for (int i = 0; i < size; ++i) - elements[i] = (jlong) n.vibrationPattern[i]; - - env->SetLongArrayRegion (array, 0, size, elements); - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setVibrate, array.get()); - } - } - - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setProgress, n.progress.max, n.progress.current, n.progress.indeterminate); - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setNumber, n.badgeNumber); - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setAutoCancel, n.shouldAutoCancel); - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOngoing, n.ongoing); - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setOnlyAlertOnce, n.alertOnlyOnce); - - if (getAndroidSDKVersion() >= 16) - { - if (n.subtitle.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setSubText, javaString (n.subtitle).get()); - - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setPriority, n.priority); - - if (getAndroidSDKVersion() < 24) - { - const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi16.setUsesChronometer, useChronometer); - } - } - - if (getAndroidSDKVersion() >= 17) - { - const bool showTimeStamp = n.timestampVisibility != PushNotifications::Notification::off; - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi17.setShowWhen, showTimeStamp); - } - - if (getAndroidSDKVersion() >= 20) - { - if (n.groupId.isNotEmpty()) - { - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroup, javaString (n.groupId).get()); - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setGroupSummary, n.groupSummary); - } - - if (n.groupSortKey.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setSortKey, javaString (n.groupSortKey).get()); - - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.setLocalOnly, n.localOnly); - - auto extras = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); - - env->CallVoidMethod (extras, AndroidBundle.putBundle, javaString ("notificationData").get(), - juceNotificationToBundle (n).get()); - - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi20.addExtras, extras.get()); - } - - if (getAndroidSDKVersion() >= 21) - { - if (n.person.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.addPerson, javaString (n.person).get()); - - auto categoryString = typeToCategory (n.type); - if (categoryString.isNotEmpty()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setCategory, javaString (categoryString).get()); - - if (n.accentColour != Colour()) - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setColor, n.accentColour.getARGB()); - - env->CallObjectMethod (notificationBuilder, NotificationBuilderApi21.setVisibility, n.lockScreenAppearance); - } - - if (getAndroidSDKVersion() >= 24) - { - const bool useChronometer = n.timestampVisibility == PushNotifications::Notification::chronometer; - const bool useCountDownChronometer = n.timestampVisibility == PushNotifications::Notification::countDownChronometer; - - 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); - } - - static void setupNotificationDeletedCallback (const PushNotifications::Notification& n, - LocalRef& notificationBuilder) - { - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - 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) (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())); - - env->CallObjectMethod (deleteIntent, AndroidIntent.setAction, actionString.get()); - env->CallObjectMethod (deleteIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get()); - - auto deletePendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, - AndroidPendingIntent.getActivity, - context.get(), - 1002, - deleteIntent.get(), - 0)); - - env->CallObjectMethod (notificationBuilder, NotificationBuilderBase.setDeleteIntent, deletePendingIntent.get()); - } - - static void setupActions (const PushNotifications::Notification& n, LocalRef& notificationBuilder) - { - if (getAndroidSDKVersion() < 16) - return; - - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - int actionIndex = 0; - - for (const auto& action : n.actions) - { - 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) (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())); - - env->CallObjectMethod (notifyIntent, AndroidIntent.setAction, actionString.get()); - // Packaging entire notification into extras bundle here, so that we can retrieve all the details later on - env->CallObjectMethod (notifyIntent, AndroidIntent.putExtras, juceNotificationToBundle (n).get()); - - auto notifyPendingIntent = LocalRef (env->CallStaticObjectMethod (AndroidPendingIntent, - AndroidPendingIntent.getActivity, - context.get(), - 1002, - notifyIntent.get(), - 0)); - - 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()); - - if (iconId == 0) - iconId = env->CallIntMethod (resources, AndroidResources.getIdentifier, javaString (n.icon).get(), - javaString ("raw").get(), packageNameString.get()); - - if (getAndroidSDKVersion() >= 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) - { - auto resultKey = javaString (action.title + String (actionIndex)); - auto remoteInputBuilder = LocalRef (env->NewObject (RemoteInputBuilder, - RemoteInputBuilder.constructor, - resultKey.get())); - - if (! action.textInputPlaceholder.isEmpty()) - env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setLabel, javaString (action.textInputPlaceholder).get()); - - if (! action.allowedResponses.isEmpty()) - { - env->CallObjectMethod (remoteInputBuilder, RemoteInputBuilder.setAllowFreeFormInput, false); - - const int size = action.allowedResponses.size(); - - auto array = LocalRef (env->NewObjectArray (size, env->FindClass ("java/lang/String"), nullptr)); - - 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 (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()); - } - - ++actionIndex; - } - } - - static LocalRef juceUrlToAndroidUri (const URL& url) - { - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - auto packageNameString = LocalRef ((jstring) (env->CallObjectMethod (context.get(), AndroidContext.getPackageName))); - - 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()); - - auto schemeString = javaString ("android.resource://"); - auto resourceString = javaString ("/" + String (id)); - auto uriString = LocalRef ((jstring) env->CallObjectMethod (schemeString, JavaString.concat, packageNameString.get())); - uriString = LocalRef ((jstring) env->CallObjectMethod (uriString, JavaString.concat, resourceString.get())); - - return LocalRef (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse, uriString.get())); - } - - static LocalRef imagetoJavaBitmap (const Image& image) - { - auto* env = getEnv(); - - Image imageToUse = image.convertedToFormat (Image::PixelFormat::ARGB); - - auto bitmapConfig = LocalRef (env->CallStaticObjectMethod (AndroidBitmapConfig, - AndroidBitmapConfig.valueOf, - javaString ("ARGB_8888").get())); - - auto bitmap = LocalRef (env->CallStaticObjectMethod (AndroidBitmap, - AndroidBitmap.createBitmap, - image.getWidth(), - image.getHeight(), - bitmapConfig.get())); - - for (int i = 0; i < image.getWidth(); ++i) - for (int j = 0; j < image.getHeight(); ++j) - env->CallVoidMethod (bitmap.get(), AndroidBitmap.setPixel, i, j, image.getPixelAt (i, j).getARGB()); - - return bitmap; - } - - static String typeToCategory (PushNotifications::Notification::Type t) - { - switch (t) - { - case PushNotifications::Notification::unspecified: return {}; - case PushNotifications::Notification::alarm: return "alarm"; - case PushNotifications::Notification::call: return "call"; - case PushNotifications::Notification::email: return "email"; - case PushNotifications::Notification::error: return "err"; - case PushNotifications::Notification::event: return "event"; - case PushNotifications::Notification::message: return "msg"; - case PushNotifications::Notification::taskProgress: return "progress"; - case PushNotifications::Notification::promo: return "promo"; - case PushNotifications::Notification::recommendation: return "recommendation"; - case PushNotifications::Notification::reminder: return "reminder"; - case PushNotifications::Notification::service: return "service"; - case PushNotifications::Notification::social: return "social"; - case PushNotifications::Notification::status: return "status"; - case PushNotifications::Notification::system: return "sys"; - case PushNotifications::Notification::transport: return "transport"; - } - - return {}; - } - - static LocalRef varToBundleWithPropertiesString (const var& varToParse) - { - auto* env = getEnv(); - - auto bundle = LocalRef (env->NewObject (AndroidBundle, AndroidBundle.constructor)); - env->CallVoidMethod (bundle, AndroidBundle.putString, javaString ("properties").get(), - javaString (JSON::toString (varToParse, false)).get()); - - return bundle; - } - - // Gets "properties" var from bundle. - static var bundleWithPropertiesStringToVar (const LocalRef& bundle) - { - auto* env = getEnv(); - - auto varString = LocalRef ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString, - javaString ("properties").get())); - - var resultVar; - JSON::parse (juceString (varString.get()), resultVar); - - // Note: We are not checking if result of parsing was okay, because there may be no properties set at all. - return resultVar; - } - - // Reverse of juceNotificationToBundle(). - static PushNotifications::Notification localNotificationBundleToJuceNotification (const LocalRef& bundle) - { - auto* env = getEnv(); - - PushNotifications::Notification n; - - if (bundle.get() != nullptr) - { - n.identifier = getStringFromBundle (env, "identifier", bundle); - n.title = getStringFromBundle (env, "title", bundle); - n.body = getStringFromBundle (env, "body", bundle); - n.subtitle = getStringFromBundle (env, "subtitle", bundle); - n.badgeNumber = getIntFromBundle (env, "badgeNumber", bundle); - n.soundToPlay = URL (getStringFromBundle (env, "soundToPlay", bundle)); - n.properties = getPropertiesVarFromBundle (env, "properties", bundle); - n.tickerText = getStringFromBundle (env, "tickerText", bundle); - n.icon = getStringFromBundle (env, "icon", bundle); - n.channelId = getStringFromBundle (env, "channelId", bundle); - - PushNotifications::Notification::Progress progress; - progress.max = getIntFromBundle (env, "progressMax", bundle); - progress.current = getIntFromBundle (env, "progressCurrent", bundle); - progress.indeterminate = getBoolFromBundle (env, "progressIndeterminate", bundle); - n.progress = progress; - - n.person = getStringFromBundle (env, "person", bundle); - n.type = (PushNotifications::Notification::Type) getIntFromBundle (env, "type", bundle); - n.priority = (PushNotifications::Notification::Priority) getIntFromBundle (env, "priority", bundle); - n.lockScreenAppearance = (PushNotifications::Notification::LockScreenAppearance) getIntFromBundle (env, "lockScreenAppearance", bundle); - n.groupId = getStringFromBundle (env, "groupId", bundle); - n.groupSortKey = getStringFromBundle (env, "groupSortKey", bundle); - n.groupSummary = getBoolFromBundle (env, "groupSummary", bundle); - n.accentColour = Colour ((uint32) getIntFromBundle (env, "accentColour", bundle)); - n.ledColour = Colour ((uint32) getIntFromBundle (env, "ledColour", bundle)); - - PushNotifications::Notification::LedBlinkPattern ledBlinkPattern; - ledBlinkPattern.msToBeOn = getIntFromBundle (env, "ledBlinkPatternMsToBeOn", bundle); - ledBlinkPattern.msToBeOff = getIntFromBundle (env, "ledBlinkPatternMsToBeOff", bundle); - n.ledBlinkPattern = ledBlinkPattern; - - n.vibrationPattern = getLongArrayFromBundle (env, "vibrationPattern", bundle); - - n.shouldAutoCancel = getBoolFromBundle (env, "shouldAutoCancel", bundle); - n.localOnly = getBoolFromBundle (env, "localOnly", bundle); - n.ongoing = getBoolFromBundle (env, "ongoing", bundle); - n.alertOnlyOnce = getBoolFromBundle (env, "alertOnlyOnce", bundle); - n.timestampVisibility = (PushNotifications::Notification::TimestampVisibility) getIntFromBundle (env, "timestampVisibility", bundle); - n.badgeIconType = (PushNotifications::Notification::BadgeIconType) getIntFromBundle (env, "badgeIconType", bundle); - n.groupAlertBehaviour = (PushNotifications::Notification::GroupAlertBehaviour) getIntFromBundle (env, "groupAlertBehaviour", bundle); - n.timeoutAfterMs = getLongFromBundle (env, "timeoutAfterMs", bundle); - } - - return n; - } - - static String getStringFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) - { - auto keyString = javaString (key); - - if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) - { - auto value = LocalRef ((jstring)env->CallObjectMethod (bundle, AndroidBundle.getString, keyString.get())); - return juceString (value); - } - - return {}; - } - - static int getIntFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) - { - auto keyString = javaString (key); - - if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) - return env->CallIntMethod (bundle, AndroidBundle.getInt, keyString.get()); - - return 0; - } - - // Converting to int on purpose! - static int getLongFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) - { - auto keyString = javaString (key); - - if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) - return (int) env->CallLongMethod (bundle, AndroidBundle.getLong, keyString.get()); - - return 0; - } - - static var getPropertiesVarFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) - { - auto keyString = javaString (key); - - if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) - { - auto value = LocalRef (env->CallObjectMethod (bundle, AndroidBundle.getBundle, keyString.get())); - return bundleWithPropertiesStringToVar (value); - } - - return {}; - } - - static bool getBoolFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) - { - auto keyString = javaString (key); - - if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) - return env->CallBooleanMethod (bundle, AndroidBundle.getBoolean, keyString.get()); - - return false; - } - - static Array getLongArrayFromBundle (JNIEnv* env, const String& key, const LocalRef& bundle) - { - auto keyString = javaString (key); - - if (env->CallBooleanMethod (bundle, AndroidBundle.containsKey, keyString.get())) - { - auto array = LocalRef ((jlongArray) env->CallObjectMethod (bundle, AndroidBundle.getLongArray, keyString.get())); - - const int size = env->GetArrayLength (array.get()); - - jlong* elements = env->GetLongArrayElements (array.get(), nullptr); - - Array resultArray; - - for (int i = 0; i < size; ++i) - resultArray.add ((int) *elements++); - - return resultArray; - } - - return {}; - } - - static PushNotifications::Notification javaNotificationToJuceNotification (const LocalRef& notification) - { - if (getAndroidSDKVersion() < 20) - return {}; - - auto* env = getEnv(); - - auto extras = LocalRef (env->GetObjectField (notification, AndroidNotification.extras)); - auto notificationData = LocalRef (env->CallObjectMethod (extras, AndroidBundle.getBundle, - javaString ("notificationData").get())); - - if (notificationData.get() != nullptr) - return localNotificationBundleToJuceNotification (notificationData); - - return remoteNotificationBundleToJuceNotification (extras); - } - - static PushNotifications::Notification remoteNotificationBundleToJuceNotification (const LocalRef& bundle) - { - // This will probably work only for remote notifications that get delivered to system tray - PushNotifications::Notification n; - n.properties = bundleToVar (bundle); - - return n; - } - - static var bundleToVar (const LocalRef& bundle) - { - if (bundle.get() == nullptr) - { - auto* env = getEnv(); - - auto keySet = LocalRef (env->CallObjectMethod (bundle, AndroidBundle.keySet)); - auto iterator = LocalRef (env->CallObjectMethod (keySet, JavaSet.iterator)); - - DynamicObject::Ptr dynamicObject = new DynamicObject(); - - for (;;) - { - if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext)) - break; - - auto key = LocalRef ((jstring) env->CallObjectMethod (iterator, JavaIterator.next)); - auto object = LocalRef (env->CallObjectMethod (bundle, AndroidBundle.get, key.get())); - - if (object.get() != nullptr) - { - auto objectAsString = LocalRef ((jstring) env->CallObjectMethod (object, JavaObject.toString)); - auto objectClass = LocalRef (env->CallObjectMethod (object, JavaObject.getClass)); - auto classAsString = LocalRef ((jstring) env->CallObjectMethod (objectClass, JavaClass.getName)); - - // Note: It seems that Firebase delivers values as strings always, so this check is rather unnecessary, - // at least until they change the behaviour. - var value = juceString (classAsString) == "java.lang.Bundle" ? bundleToVar (object) : var (juceString (objectAsString.get())); - dynamicObject->setProperty (juceString (key.get()), value); - } - else - { - dynamicObject->setProperty (juceString (key.get()), {}); - } - } - - return var (dynamicObject.get()); - } - - return {}; - } - - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - static PushNotifications::Notification firebaseRemoteNotificationToJuceNotification (jobject remoteNotification) - { - auto* env = getEnv(); - - auto collapseKey = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getCollapseKey)); - auto from = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getFrom)); - auto messageId = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageId)); - auto messageType = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getMessageType)); - auto to = LocalRef ((jstring) env->CallObjectMethod (remoteNotification, RemoteMessage.getTo)); - auto notification = LocalRef (env->CallObjectMethod (remoteNotification, RemoteMessage.getNotification)); - auto data = LocalRef (env->CallObjectMethod (remoteNotification, RemoteMessage.getData)); - - const int64 sentTime = env->CallLongMethod (remoteNotification, RemoteMessage.getSentTime); - const int ttl = env->CallIntMethod (remoteNotification, RemoteMessage.getTtl); - - auto keySet = LocalRef (env->CallObjectMethod (data, JavaMap.keySet)); - auto iterator = LocalRef (env->CallObjectMethod (keySet, JavaSet.iterator)); - - DynamicObject::Ptr dataDynamicObject = new DynamicObject(); - - for (;;) - { - if (! env->CallBooleanMethod (iterator, JavaIterator.hasNext)) - break; - - auto key = LocalRef ((jstring) env->CallObjectMethod (iterator, JavaIterator.next)); - auto value = LocalRef ((jstring) env->CallObjectMethod (data, JavaMap.get, key.get())); - - dataDynamicObject->setProperty (juceString (key.get()), juceString (value.get())); - } - - var dataVar (dataDynamicObject.get()); - - DynamicObject::Ptr propertiesDynamicObject = new DynamicObject(); - propertiesDynamicObject->setProperty ("collapseKey", juceString (collapseKey.get())); - propertiesDynamicObject->setProperty ("from", juceString (from.get())); - propertiesDynamicObject->setProperty ("messageId", juceString (messageId.get())); - propertiesDynamicObject->setProperty ("messageType", juceString (messageType.get())); - propertiesDynamicObject->setProperty ("to", juceString (to.get())); - propertiesDynamicObject->setProperty ("sentTime", sentTime); - propertiesDynamicObject->setProperty ("ttl", ttl); - propertiesDynamicObject->setProperty ("data", dataVar); - - PushNotifications::Notification n; - - if (notification != 0) - { - auto body = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBody)); - auto bodyLocalizationKey = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationKey)); - auto clickAction = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getClickAction)); - auto color = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getColor)); - auto icon = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getIcon)); - auto sound = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getSound)); - auto tag = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTag)); - auto title = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitle)); - auto titleLocalizationKey = LocalRef ((jstring) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationKey)); - auto link = LocalRef (env->CallObjectMethod (notification, RemoteMessageNotification.getLink)); - - auto bodyLocalizationArgs = LocalRef ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getBodyLocalizationArgs)); - auto titleLocalizationArgs = LocalRef ((jobjectArray) env->CallObjectMethod (notification, RemoteMessageNotification.getTitleLocalizationArgs)); - - n.identifier = juceString (tag.get()); - n.title = juceString (title.get()); - n.body = juceString (body.get()); - n.soundToPlay = URL (juceString (sound.get())); - - auto colourString = juceString (color.get()).substring (1); - const uint8 r = (uint8) colourString.substring (0, 2).getIntValue(); - const uint8 g = (uint8) colourString.substring (2, 4).getIntValue(); - const uint8 b = (uint8) colourString.substring (4, 6).getIntValue(); - n.accentColour = Colour (r, g, b); - - // Note: Ignoring the icon, because Firebase passes it as a string. - - propertiesDynamicObject->setProperty ("clickAction", juceString (clickAction.get())); - propertiesDynamicObject->setProperty ("bodyLocalizationKey", juceString (bodyLocalizationKey.get())); - propertiesDynamicObject->setProperty ("titleLocalizationKey", juceString (titleLocalizationKey.get())); - propertiesDynamicObject->setProperty ("bodyLocalizationArgs", javaStringArrayToJuce (bodyLocalizationArgs)); - propertiesDynamicObject->setProperty ("titleLocalizationArgs", javaStringArrayToJuce (titleLocalizationArgs)); - propertiesDynamicObject->setProperty ("link", link.get() != nullptr ? juceString ((jstring) env->CallObjectMethod (link, AndroidUri.toString)) : String()); - } - - n.properties = var (propertiesDynamicObject.get()); - - return n; - } - #endif - - void setupChannels (const Array& groups, const Array& channels) - { - if (getAndroidSDKVersion() < 26) - return; - - auto* env = getEnv(); - - auto notificationManager = getNotificationManager(); - - jassert (notificationManager.get() != nullptr); - - if (notificationManager.get() == nullptr) - return; - - for (const auto& g : groups) - { - // Channel group identifier and name have to be set. - jassert (g.identifier.isNotEmpty() && g.name.isNotEmpty()); - - if (g.identifier.isNotEmpty() && g.name.isNotEmpty()) - { - auto group = LocalRef (env->NewObject (NotificationChannelGroup, NotificationChannelGroup.constructor, - javaString (g.identifier).get(), javaString (g.name).get())); - env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannelGroup, group.get()); - } - } - - for (const auto& c : channels) - { - // Channel identifier, name and group have to be set. - jassert (c.identifier.isNotEmpty() && c.name.isNotEmpty() && c.groupId.isNotEmpty()); - - if (c.identifier.isEmpty() || c.name.isEmpty() || c.groupId.isEmpty()) - continue; - - auto channel = LocalRef (env->NewObject (NotificationChannel, NotificationChannel.constructor, - javaString (c.identifier).get(), javaString (c.name).get(), c.importance)); - - env->CallVoidMethod (channel, NotificationChannel.enableLights, c.enableLights); - env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration); - env->CallVoidMethod (channel, NotificationChannel.setBypassDnd, c.bypassDoNotDisturb); - env->CallVoidMethod (channel, NotificationChannel.setDescription, javaString (c.description).get()); - env->CallVoidMethod (channel, NotificationChannel.setGroup, javaString (c.groupId).get()); - env->CallVoidMethod (channel, NotificationChannel.setImportance, c.importance); - env->CallVoidMethod (channel, NotificationChannel.setLightColor, c.ledColour.getARGB()); - env->CallVoidMethod (channel, NotificationChannel.setLockscreenVisibility, c.lockScreenAppearance); - env->CallVoidMethod (channel, NotificationChannel.setShowBadge, c.canShowBadge); - - - const int size = c.vibrationPattern.size(); - - if (size > 0) - { - auto array = LocalRef (env->NewLongArray (size)); - jlong* elements = env->GetLongArrayElements (array, nullptr); - - for (int i = 0; i < size; ++i) - elements[i] = (jlong) c.vibrationPattern[i]; - - env->SetLongArrayRegion (array, 0, size, elements); - env->CallVoidMethod (channel, NotificationChannel.setVibrationPattern, array.get()); - - env->CallVoidMethod (channel, NotificationChannel.enableVibration, c.enableVibration); - } - - LocalRef builder (env->NewObject (AndroidAudioAttributesBuilder, AndroidAudioAttributesBuilder.constructor)); - const int contentTypeSonification = 4; - const int usageNotification = 5; - env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setContentType, contentTypeSonification); - env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.setUsage, usageNotification); - auto audioAttributes = LocalRef (env->CallObjectMethod (builder.get(), AndroidAudioAttributesBuilder.build)); - env->CallVoidMethod (channel, NotificationChannel.setSound, juceUrlToAndroidUri (c.soundToPlay).get(), audioAttributes.get()); - - env->CallVoidMethod (notificationManager, NotificationManagerApi26.createNotificationChannel, channel.get()); - } - } - - void getPendingLocalNotifications() const {} - void removePendingLocalNotification (const String&) {} - void removeAllPendingLocalNotifications() {} - - static bool intentActionContainsAnyOf (jobject intent, const StringArray& strings, bool includePackageName) - { - auto* env = getEnv(); - LocalRef context (getMainActivity()); - - String packageName = includePackageName ? juceString ((jstring) env->CallObjectMethod (context.get(), - AndroidContext.getPackageName)) - : String{}; - - String intentAction = juceString ((jstring) env->CallObjectMethod (intent, AndroidIntent.getAction)); - - for (const auto& string : strings) - if (intentAction.contains (packageName + string)) - return true; - - return false; - } - - static bool isDeleteNotificationIntent (jobject intent) - { - return intentActionContainsAnyOf (intent, StringArray (".JUCE_NOTIFICATION_DELETED"), true); - } - - static bool isLocalNotificationIntent (jobject intent) - { - return intentActionContainsAnyOf (intent, { ".JUCE_NOTIFICATION.", - ".JUCE_NOTIFICATION_BUTTON_ACTION.", - ".JUCE_NOTIFICATION_TEXT_INPUT_ACTION." }, - true); - } - - static bool isRemoteNotificationIntent (jobject intent) - { - auto* env = getEnv(); - - auto categories = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getCategories)); - - int categoriesNum = categories != nullptr - ? env->CallIntMethod (categories, JavaSet.size) - : 0; - - if (categoriesNum == 0) - return false; - - if (! env->CallBooleanMethod (categories, JavaSet.contains, javaString ("android.intent.category.LAUNCHER").get())) - return false; - - if (! intentActionContainsAnyOf (intent, StringArray ("android.intent.action.MAIN"), false)) - return false; - - auto extras = LocalRef (env->CallObjectMethod (intent, AndroidIntent.getExtras)); - - if (extras == nullptr) - return false; - - return env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.sent_time").get()) - && env->CallBooleanMethod (extras, AndroidBundle.containsKey, javaString ("google.message_id").get()); - } - - PushNotifications& owner; -}; - -#if defined(JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME) -//============================================================================== -struct JuceFirebaseInstanceIdService -{ - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (tokenRefreshed, "firebaseInstanceIdTokenRefreshed", "(Ljava/lang/String;)V") - - DECLARE_JNI_CLASS (InstanceIdService, "com/rmsl/juce/JuceFirebaseInstanceIdService") - #undef JNI_CLASS_MEMBERS - - static void JNICALL tokenRefreshed (JNIEnv*, jobject /*instanceIdService*/, void* token) - { - if (auto* instance = PushNotifications::getInstanceWithoutCreating()) - instance->pimpl->notifyListenersTokenRefreshed (juceString (static_cast (token))); - } -}; - -JuceFirebaseInstanceIdService::InstanceIdService_Class JuceFirebaseInstanceIdService::InstanceIdService; -#endif - -#if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) -//============================================================================== -struct JuceFirebaseMessagingService -{ - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - CALLBACK (remoteNotificationReceived, "firebaseRemoteMessageReceived", "(Lcom/google/firebase/messaging/RemoteMessage;)V") \ - CALLBACK (remoteMessagesDeleted, "firebaseRemoteMessagesDeleted", "()V") \ - CALLBACK (remoteMessageSent, "firebaseRemoteMessageSent", "(Ljava/lang/String;)V") \ - CALLBACK (remoteMessageSendError, "firebaseRemoteMessageSendError", "(Ljava/lang/String;Ljava/lang/String;)V") - - DECLARE_JNI_CLASS (MessagingService, "com/rmsl/juce/JuceFirebaseMessagingService") - #undef JNI_CLASS_MEMBERS - - static void JNICALL remoteNotificationReceived (JNIEnv*, jobject /*messagingService*/, void* remoteMessage) - { - if (auto* instance = PushNotifications::getInstanceWithoutCreating()) - instance->pimpl->notifyListenersAboutRemoteNotificationFromService (LocalRef (static_cast (remoteMessage))); - - } - - static void JNICALL remoteMessagesDeleted() - { - if (auto* instance = PushNotifications::getInstanceWithoutCreating()) - instance->pimpl->notifyListenersAboutRemoteNotificationsDeleted(); - } - - static void JNICALL remoteMessageSent (JNIEnv*, jobject /*messagingService*/, void* messageId) - { - if (auto* instance = PushNotifications::getInstanceWithoutCreating()) - instance->pimpl->notifyListenersAboutUpstreamMessageSent (LocalRef (static_cast (messageId))); - } - - static void JNICALL remoteMessageSendError (JNIEnv*, jobject /*messagingService*/, void* messageId, void* error) - { - if (auto* instance = PushNotifications::getInstanceWithoutCreating()) - instance->pimpl->notifyListenersAboutUpstreamMessageSendingError (LocalRef (static_cast (messageId)), - LocalRef (static_cast (error))); - } -}; - -JuceFirebaseMessagingService::MessagingService_Class JuceFirebaseMessagingService::MessagingService; -#endif - -//============================================================================== -bool juce_handleNotificationIntent (void* intent) -{ - auto* instance = PushNotifications::getInstanceWithoutCreating(); - - if (PushNotifications::Pimpl::isDeleteNotificationIntent ((jobject) intent)) - { - if (instance) - instance->pimpl->notifyListenersAboutLocalNotificationDeleted (LocalRef ((jobject) intent)); - - return true; - } - else if (PushNotifications::Pimpl::isLocalNotificationIntent ((jobject) intent)) - { - if (instance) - instance->pimpl->notifyListenersAboutLocalNotification (LocalRef ((jobject) intent)); - - return true; - } - #if defined(JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME) - else if (PushNotifications::Pimpl::isRemoteNotificationIntent ((jobject) intent)) - { - if (instance) - instance->pimpl->notifyListenersAboutRemoteNotificationFromSystemTray (LocalRef ((jobject) intent)); - - return true; - } - #endif - - return false; -} - -} // namespace juce diff --git a/source/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp b/source/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp deleted file mode 100644 index 35adf391c..000000000 --- a/source/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp +++ /dev/null @@ -1,725 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -//============================================================================== -// This byte-code is generated from native/java/com/rmsl/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,150,114,161,94,0,3,74,117,99,101,87,101,98,86,105,101,119,49,54,66,121,116,101,67,111,100,101,46,100,101,120,0,125, -150,93,108,20,85,20,199,207,124,236,78,119,218,110,183,5,74,191,40,109,69,168,72,89,176,162,165,11,88,40,159,101,81,161,88,226, -106,34,211,221,107,59,101,118,102,153,153,109,27,67,16,161,137,134,240,96,4,222,72,140,9,18,35,62,18,195,131,15,4,53,250,226,155, -209,23,30,212,4,195,131,15,198,24,98,20,19,255,119,238,221,101,129,226,110,126,123,206,61,231,220,123,207,189,247,236,204, -45,176,121,115,195,224,38,242,254,253,180,235,204,157,47,183,223,188,240,115,199,231,119,79,174,251,240,23,243,246,209,206,219, -79,221,168,39,42,17,209,252,196,179,45,36,63,247,76,162,17,18,246,165,92,42,68,141,144,55,32,117,200,215,85,162,37,144,39,32, -53,30,131,159,108,29,81,8,217,16,71,27,244,129,181,96,0,188,0,118,130,55,192,9,240,1,184,6,126,0,247,64,171,65,244,28,56,10,22, -192,71,224,107,112,27,212,97,220,149,96,16,236,1,99,224,69,48,14,94,5,71,65,1,216,192,3,62,152,7,111,131,115,224,60,184,4,62, -6,87,193,117,112,19,124,7,126,4,191,130,223,192,63,160,62,65,212,9,214,130,109,96,31,176,64,17,204,131,147,224,12,56,11,222,7,87, -193,55,224,39,240,23,104,54,197,126,96,73,132,212,9,67,18,204,4,51,97,155,169,129,196,62,38,65,19,72,129,102,192,55,126,137, -220,235,101,160,21,44,7,43,65,76,142,119,57,38,108,149,67,106,147,250,103,176,183,75,253,26,244,78,169,127,1,189,67,234,223,66, -239,146,250,247,208,187,165,126,11,250,10,169,95,174,177,223,169,209,255,132,222,35,243,227,227,244,74,157,39,197,215,182,58, -90,99,138,250,229,58,87,71,82,180,99,164,144,8,53,35,89,39,219,9,82,165,140,211,64,36,27,104,125,36,53,26,150,50,19,141,35,226, -76,244,91,19,201,58,74,71,50,65,27,34,105,208,70,57,239,96,36,99,180,37,146,245,180,53,146,58,109,139,246,94,204,155,170,206, -79,145,22,147,123,201,107,58,68,227,138,72,51,26,79,145,231,87,241,47,192,255,149,244,215,75,127,170,198,127,1,254,63,164,159, -103,189,0,253,172,121,95,63,111,138,62,151,76,30,175,69,122,187,41,234,161,148,226,190,62,140,87,74,241,61,127,45,165,80,174, -69,212,137,142,17,248,248,171,77,81,7,227,56,140,210,72,156,212,141,73,172,62,22,249,6,76,81,111,194,103,192,215,18,213,87,101, -158,231,171,243,168,15,205,163,97,30,53,154,71,156,149,66,59,77,81,167,135,183,107,180,66,105,69,250,185,29,42,117,43,73,140, -208,173,172,145,245,168,224,155,192,156,90,212,62,96,138,122,30,31,81,137,247,192,153,168,155,225,75,70,150,210,68,146,244,131, -253,127,243,253,212,163,248,9,83,172,173,54,126,8,163,137,232,38,68,39,177,199,122,180,222,163,166,168,183,241,210,35,99,251, -42,25,199,141,5,227,162,113,101,54,206,207,182,255,46,63,27,158,147,74,51,232,247,4,175,97,229,240,105,172,68,29,95,64,127,12, -184,81,211,227,67,90,3,241,118,41,215,68,123,47,154,52,132,185,186,213,102,165,91,237,83,227,212,161,109,194,46,42,244,76,179, -209,219,127,183,17,214,53,242,185,183,4,115,244,70,187,196,191,61,82,226,89,99,10,191,216,213,100,244,28,172,253,156,122,168,125, -238,161,54,175,17,3,79,2,165,166,205,45,122,85,170,164,73,189,89,214,30,63,111,173,234,173,232,98,12,174,55,227,219,36,107, -211,64,230,75,96,141,111,177,93,59,220,70,13,163,211,190,87,100,163,142,205,220,144,226,82,42,99,148,26,43,231,217,17,54,57,97, -179,185,245,51,214,172,69,90,54,155,165,246,172,229,22,124,207,46,164,167,124,171,52,109,231,131,244,14,59,44,90,165,12,117, -86,93,46,11,211,211,97,88,74,143,7,206,46,223,247,252,12,45,173,58,189,32,125,128,5,129,53,197,50,212,83,181,206,177,201,99,118, -88,237,176,23,118,135,249,139,68,32,165,218,148,51,180,106,145,136,67,44,240,202,126,158,65,150,60,55,192,76,109,139,68,241, -165,101,168,251,49,158,202,248,253,217,188,87,76,251,197,192,73,207,96,75,210,53,251,178,234,193,76,250,254,47,82,198,116,62,62, -134,15,80,176,156,89,251,88,218,114,93,47,180,66,219,115,211,187,220,188,227,5,182,59,53,234,88,65,192,211,125,52,102,159, -235,50,95,250,123,23,241,31,96,197,73,25,192,16,178,44,203,207,51,109,123,232,88,42,135,227,161,207,172,98,134,90,132,217,177, -220,169,244,75,147,51,44,31,62,104,67,28,210,200,144,50,65,234,196,24,105,19,99,89,210,241,147,165,24,255,205,194,154,133,53, -203,173,188,169,228,72,207,69,238,92,54,151,203,82,189,149,207,227,224,119,59,214,84,64,49,198,143,153,140,55,173,89,59,239,185, -100,76,139,19,39,125,218,11,66,170,231,191,59,153,195,66,86,160,58,222,200,122,249,99,148,224,218,97,239,149,128,81,157,29, -236,180,45,199,155,162,70,59,128,193,223,195,130,176,236,51,210,93,171,200,168,209,115,71,177,111,236,136,237,22,188,57,74,162, -137,85,134,53,237,151,81,129,187,241,39,8,166,49,69,163,104,143,135,150,207,103,108,241,220,67,44,207,236,89,86,168,84,36,37, -124,22,148,157,240,64,48,69,173,193,180,87,118,10,251,220,144,161,200,74,225,33,118,188,140,217,201,20,246,172,103,21,40,17,178, -121,254,47,40,58,164,135,211,118,64,90,217,119,40,54,107,57,101,228,56,139,243,166,246,185,74,165,85,19,173,140,212,81,113, -213,36,93,241,45,151,62,158,48,159,170,186,136,214,135,28,149,213,84,58,60,178,164,248,156,216,13,71,89,107,36,213,229,25,117, -102,110,128,2,101,196,72,230,232,45,125,248,233,117,131,92,27,136,188,155,51,234,94,120,7,201,72,110,221,223,221,69,25,117,120, -200,72,158,237,162,253,218,240,208,147,70,242,221,28,141,106,195,171,87,69,182,131,220,185,98,235,123,51,26,109,90,58,208,27,163, -206,149,231,113,13,48,146,164,54,40,67,109,245,106,163,218,167,39,214,45,87,42,138,170,38,149,161,46,181,45,209,134,23,189, -166,146,170,180,104,239,156,210,47,24,218,105,188,167,128,174,220,48,20,229,22,94,104,122,76,133,183,14,222,123,70,92,122,57,9, -229,147,58,68,128,115,9,69,185,14,126,79,240,231,99,51,34,111,153,149,247,179,82,35,71,72,220,107,249,51,179,114,183,229,207, -203,218,251,109,229,142,27,163,251,247,220,56,221,191,235,106,41,161,243,231,188,210,35,238,11,23,160,199,123,164,29,29,149,148, -176,243,123,149,218,35,230,229,119,99,77,198,243,119,191,222,35,239,22,220,32,251,70,119,144,148,200,149,223,195,255,3,213, -111,243,3,192,11,0,0,0,0}; - -//============================================================================== -// This byte-code is generated from native/javacore/app/com/rmsl/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,45,103,161,94,0,3,74,117,99,101,87,101,98,86,105,101,119,50,49,46,100,101,120,0,141,151,93,140,27,87,21,199,207, -204,216,30,219,99,59,182,55,251,145,143,221,110,210,173,178,105,154,186,155,164,52,169,211,106,241,38,219,221,48,41,52,155,108, -138,43,85,154,181,47,235,73,188,51,206,204,120,119,65,162,132,80,148,138,34,148,168,20,181,125,129,135,16,129,4,18,168,125,136, -42,224,133,207,74,60,160,138,135,208,71,210,151,162,128,242,148,86,136,7,254,247,99,28,111,18,34,108,253,124,206,61,231,220, -143,115,239,153,241,76,147,173,103,159,216,255,36,253,228,121,251,31,197,127,189,252,251,226,31,94,89,242,217,87,31,123,227, -151,175,95,184,28,222,168,188,152,39,234,16,209,250,226,129,50,169,207,247,115,68,243,36,237,67,188,173,17,149,32,111,66,38,32, -175,233,68,195,144,215,33,13,200,75,248,105,101,136,110,65,222,74,17,125,6,82,38,81,1,148,192,35,96,18,236,5,115,224,37,176,14, -190,7,126,1,62,4,159,130,209,52,209,83,224,52,248,22,248,41,248,19,184,9,114,24,191,2,102,65,29,120,224,28,232,130,175,129, -243,224,34,120,29,92,2,63,0,111,131,31,130,171,224,93,240,62,248,0,124,8,62,2,55,192,39,224,54,160,44,145,5,6,193,4,120,20,28, -2,243,224,203,160,5,190,14,46,131,119,192,143,193,175,193,7,224,175,224,35,240,49,248,4,220,2,183,65,201,66,206,96,22,188,12, -214,193,101,75,238,25,210,37,164,69,106,106,130,153,176,237,132,227,160,2,216,4,138,36,247,157,31,204,0,216,12,6,213,153,240, -253,31,1,91,192,86,176,19,36,129,174,206,48,165,198,111,165,164,125,64,217,183,170,113,248,103,155,210,59,136,217,174,244,117, -232,99,74,63,223,167,127,23,250,168,210,223,130,254,144,210,175,64,223,161,244,159,245,233,215,160,143,43,253,119,125,246,63, -247,233,215,161,63,172,114,226,99,78,40,253,227,148,220,143,61,98,95,202,180,87,237,205,30,33,101,91,199,247,89,145,167,33,114, -225,251,185,91,228,92,16,237,172,178,91,162,98,185,204,208,62,33,139,180,95,200,36,213,148,156,17,227,202,184,28,250,61,38,100, -142,14,8,153,167,39,133,180,232,115,66,102,233,41,33,53,122,90,200,52,29,17,114,19,29,21,210,164,89,33,83,244,156,56,79,185, -142,114,111,61,132,158,242,124,248,135,207,122,16,141,253,57,82,243,75,127,182,207,63,7,255,11,202,159,87,254,114,159,255,69, -248,47,42,63,63,255,18,244,225,220,29,125,60,39,251,236,206,241,120,67,232,111,91,114,172,78,81,67,123,39,198,235,20,121,93,189, -132,118,189,44,107,48,129,17,248,248,87,45,185,222,5,28,104,103,58,77,250,84,1,89,38,133,239,231,150,220,123,233,203,192,87,22, -181,27,207,115,173,55,79,226,174,121,12,204,163,139,121,146,34,82,163,63,90,50,255,147,159,55,104,84,27,194,242,235,53,157, -198,180,2,70,24,211,118,137,58,78,17,95,111,6,115,26,162,253,23,75,94,43,11,211,58,241,30,83,72,251,16,124,5,97,233,44,150,40, -241,194,228,191,121,93,36,68,252,223,44,153,91,127,252,65,140,38,163,203,136,46,136,154,225,249,222,176,228,117,179,208,185, -103,236,64,39,243,156,249,170,249,166,121,117,53,53,128,21,77,222,166,94,191,127,254,159,253,54,247,250,241,92,116,250,212,146, -53,94,214,78,94,192,14,232,11,175,162,63,6,156,50,18,169,131,198,102,226,237,160,168,163,54,114,134,87,228,247,145,156,209, -89,40,211,220,155,89,58,136,185,199,244,146,54,166,239,212,211,180,213,56,140,211,48,104,95,201,220,49,121,59,15,235,46,117, -239,222,134,57,119,139,221,230,223,73,37,177,239,57,233,151,167,83,16,247,242,254,207,127,238,106,167,19,27,219,188,214,248,169, -104,125,109,110,73,40,153,196,106,99,221,196,105,24,74,31,80,245,204,239,131,70,47,50,214,77,113,15,211,85,164,161,164,166,252, -3,248,150,212,53,144,193,157,99,144,239,255,97,215,115,163,103,41,55,211,10,252,21,54,211,118,153,23,81,74,73,237,24,149,143, -117,27,236,52,91,90,116,217,218,190,169,199,207,56,171,14,105,54,25,182,109,211,22,219,241,154,129,239,54,43,203,129,211,105, -185,141,176,82,115,163,21,167,83,165,82,207,229,177,168,114,42,112,171,180,109,131,169,21,69,157,202,66,216,62,26,4,126,80,165, -205,61,167,31,86,142,179,48,116,150,89,149,198,123,214,53,182,116,214,141,122,29,230,96,111,179,224,62,17,88,106,127,42,85,122, -248,62,17,39,88,232,119,131,6,59,193,206,117,89,136,160,137,7,6,133,29,223,11,177,156,145,251,68,241,125,169,210,216,255,240, -196,139,120,212,110,248,43,149,96,37,108,87,206,96,63,43,27,54,117,98,227,130,39,30,28,171,162,70,31,20,85,165,157,118,211,105, -175,186,103,43,142,231,249,145,19,185,190,87,57,234,53,218,126,232,122,203,51,109,39,12,249,162,239,141,153,247,60,22,40,255, -142,251,248,143,179,149,37,21,192,16,50,104,243,130,168,184,62,58,118,186,209,66,20,48,103,165,74,101,105,110,59,222,114,229, -139,75,103,88,35,218,104,67,28,150,81,37,109,145,244,197,99,100,44,30,179,41,129,31,155,146,252,215,134,21,37,182,104,115,43, -111,106,117,74,212,133,187,110,215,235,54,89,78,163,129,26,153,109,59,203,33,37,25,175,8,202,11,17,31,22,153,95,113,86,221,134, -239,81,106,153,69,167,130,54,153,45,89,51,148,104,249,97,68,22,255,61,194,218,44,98,77,74,243,134,237,55,206,82,134,107,39, -253,83,24,33,237,134,71,92,167,237,47,83,222,13,97,8,158,67,169,116,3,70,9,207,89,97,148,247,189,25,108,39,59,237,122,77,127, -141,10,104,34,249,168,175,253,37,212,240,44,46,175,176,133,41,242,178,189,16,57,1,159,113,192,247,78,176,6,115,87,89,115,14,87, -130,40,106,42,223,49,198,133,78,102,32,107,148,50,1,11,187,237,232,120,184,76,67,97,203,239,182,155,243,94,196,80,159,157,72, -149,49,101,165,221,246,157,38,101,34,182,206,175,178,149,54,37,162,150,27,82,58,242,229,182,147,209,197,118,36,87,157,118,23, -185,172,162,96,104,203,90,92,174,189,132,226,49,183,198,174,190,228,98,223,176,242,241,196,248,164,189,100,135,238,114,196,89, -143,40,251,189,169,15,223,229,233,229,159,90,147,251,185,166,85,204,130,62,92,213,207,172,237,165,87,180,121,179,80,167,111, -107,137,218,19,83,79,115,245,113,225,254,45,85,245,223,124,3,1,7,200,44,60,243,133,177,237,116,88,175,77,155,133,239,108,39, -219,168,77,239,54,11,23,235,116,194,168,29,154,20,182,35,70,109,207,46,161,213,245,218,33,115,244,153,215,254,110,208,225,161, -189,59,146,180,237,161,55,112,223,55,11,164,23,180,233,145,188,190,73,127,36,145,153,26,214,98,69,215,139,218,244,118,125,196, -26,201,145,110,224,233,74,43,39,191,121,62,113,41,109,92,208,73,3,41,237,87,105,77,187,142,127,255,100,74,135,55,11,239,103, -105,83,121,99,44,237,74,6,81,224,181,172,166,189,7,110,130,43,22,191,201,15,162,199,143,248,255,123,81,253,71,104,125,50,126, -223,224,247,250,248,157,131,223,227,251,223,59,226,119,15,254,63,30,191,127,164,232,206,59,136,81,148,58,255,239,210,198,229, -179,22,30,59,40,53,46,237,252,57,75,43,202,231,17,254,92,172,143,203,121,249,59,139,161,226,249,115,81,98,92,206,197,159,157,72, -245,221,175,22,206,215,202,223,143,254,11,250,146,74,12,88,13,0,0,0,0}; - - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(Landroid/content/Context;)V") \ - METHOD (getSettings, "getSettings", "()Landroid/webkit/WebSettings;") \ - METHOD (canGoBack, "canGoBack", "()Z") \ - METHOD (goBack, "goBack", "()V") \ - METHOD (goForward, "goForward", "()V") \ - METHOD (loadDataWithBaseURL, "loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V") \ - METHOD (loadUrl, "loadUrl", "(Ljava/lang/String;Ljava/util/Map;)V") \ - METHOD (postUrl, "postUrl", "(Ljava/lang/String;[B)V") \ - METHOD (reload, "reload", "()V") \ - METHOD (setWebChromeClient, "setWebChromeClient", "(Landroid/webkit/WebChromeClient;)V") \ - METHOD (setWebViewClient, "setWebViewClient", "(Landroid/webkit/WebViewClient;)V") \ - METHOD (stopLoading, "stopLoading", "()V") - -DECLARE_JNI_CLASS (AndroidWebView, "android/webkit/WebView") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - METHOD (constructor, "", "()V") - -DECLARE_JNI_CLASS (AndroidWebViewClient, "android/webkit/WebViewClient") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - METHOD (setBuiltInZoomControls, "setBuiltInZoomControls", "(Z)V") \ - METHOD (setDisplayZoomControls, "setDisplayZoomControls", "(Z)V") \ - METHOD (setJavaScriptEnabled, "setJavaScriptEnabled", "(Z)V") \ - METHOD (setSupportMultipleWindows, "setSupportMultipleWindows", "(Z)V") - -DECLARE_JNI_CLASS (WebSettings, "android/webkit/WebSettings") -#undef JNI_CLASS_MEMBERS - -#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, CALLBACK) \ - STATICMETHOD (encode, "encode", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;") - -DECLARE_JNI_CLASS (URLEncoder, "java/net/URLEncoder") -#undef JNI_CLASS_MEMBERS - -//============================================================================== -class WebBrowserComponent::Pimpl : public AndroidViewComponent, - public AsyncUpdater -{ -public: - Pimpl (WebBrowserComponent& o) - : owner (o) - { - auto* env = getEnv(); - - setView (env->NewObject (AndroidWebView, AndroidWebView.constructor, getMainActivity().get())); - - auto settings = LocalRef (env->CallObjectMethod ((jobject) getView(), AndroidWebView.getSettings)); - env->CallVoidMethod (settings, WebSettings.setJavaScriptEnabled, true); - env->CallVoidMethod (settings, WebSettings.setBuiltInZoomControls, true); - env->CallVoidMethod (settings, WebSettings.setDisplayZoomControls, false); - env->CallVoidMethod (settings, WebSettings.setSupportMultipleWindows, true); - - juceWebChromeClient = GlobalRef (LocalRef (env->NewObject (JuceWebChromeClient, JuceWebChromeClient.constructor, - reinterpret_cast (this)))); - env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get()); - - 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()); - } - - ~Pimpl() - { - auto* env = getEnv(); - - env->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading); - - auto defaultChromeClient = LocalRef (env->NewObject (AndroidWebChromeClient, AndroidWebChromeClient.constructor)); - auto defaultViewClient = LocalRef (env->NewObject (AndroidWebViewClient, AndroidWebViewClient .constructor)); - - env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, defaultChromeClient.get()); - env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebViewClient, defaultViewClient .get()); - - masterReference.clear(); - - // if other Java thread is waiting for us to respond to page load request - // wake it up immediately (false answer will be sent), so that it releases - // the lock we need when calling hostDeleted. - responseReadyEvent.signal(); - - env->CallVoidMethod (juceWebViewClient, getAndroidSDKVersion() >= 21 ? JuceWebViewClient21.hostDeleted - : JuceWebViewClient16.hostDeleted); - } - - void goToURL (const String& url, - const StringArray* headers, - const MemoryBlock* postData) - { - auto* env = getEnv(); - - if (headers == nullptr && postData == nullptr) - { - env->CallVoidMethod ((jobject) getView(), AndroidWebView.loadUrl, javaString (url).get(), 0); - } - else if (headers != nullptr && postData == nullptr) - { - auto headersMap = LocalRef (env->NewObject (JavaHashMap, - JavaHashMap.constructorWithCapacity, - headers->size())); - - for (const auto& header : *headers) - { - auto name = header.upToFirstOccurrenceOf (":", false, false).trim(); - auto value = header.fromFirstOccurrenceOf (":", false, false).trim(); - - env->CallObjectMethod (headersMap, JavaMap.put, - javaString (name).get(), - javaString (value).get()); - } - - env->CallVoidMethod ((jobject) getView(), AndroidWebView.loadUrl, - javaString (url).get(), headersMap.get()); - } - else if (headers == nullptr && postData != nullptr) - { - auto dataStringJuce = postData->toString(); - auto dataStringJava = javaString (dataStringJuce); - auto encodingString = LocalRef (env->CallStaticObjectMethod (URLEncoder, URLEncoder.encode, - dataStringJava.get(), javaString ("utf-8").get())); - - auto bytes = LocalRef ((jbyteArray) env->CallObjectMethod (encodingString, JavaString.getBytes)); - - env->CallVoidMethod ((jobject) getView(), AndroidWebView.postUrl, - javaString (url).get(), bytes.get()); - } - else if (headers != nullptr && postData != nullptr) - { - // There is no support for both extra headers and post data in Android WebView, so - // we need to open URL manually. - - URL urlToUse = URL (url).withPOSTData (*postData); - connectionThread.reset (new ConnectionThread (*this, urlToUse, *headers)); - } - } - - void stop() - { - connectionThread = nullptr; - - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading); - } - - void goBack() - { - connectionThread = nullptr; - - auto* env = getEnv(); - auto view = (jobject) getView(); - - if (env->CallBooleanMethod (view, AndroidWebView.canGoBack)) - env->CallVoidMethod (view, AndroidWebView.goBack); - else - owner.reloadLastURL(); - } - - void goForward() - { - connectionThread = nullptr; - - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.goForward); - } - - void refresh() - { - connectionThread = nullptr; - - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.reload); - } - - void handleAsyncUpdate() - { - jassert (connectionThread != nullptr); - - if (connectionThread == nullptr) - return; - - auto& result = connectionThread->getResult(); - - if (result.statusCode >= 200 && result.statusCode < 300) - { - auto url = javaString (result.url); - auto data = javaString (result.data); - auto mimeType = javaString ("text/html"); - auto encoding = javaString ("utf-8"); - - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.loadDataWithBaseURL, - url.get(), data.get(), mimeType.get(), - encoding.get(), 0); - } - else - { - owner.pageLoadHadNetworkError (result.description); - } - } - - bool handlePageAboutToLoad (const String& url) - { - if (MessageManager::getInstance()->isThisTheMessageThread()) - return owner.pageAboutToLoad (url); - - WeakReference weakRef (this); - - if (weakRef == nullptr) - return false; - - responseReadyEvent.reset(); - - bool shouldLoad = false; - - MessageManager::callAsync ([weakRef, url, &shouldLoad] - { - if (weakRef == nullptr) - return; - - shouldLoad = weakRef->owner.pageAboutToLoad (url); - - weakRef->responseReadyEvent.signal(); - }); - - responseReadyEvent.wait (-1); - - return shouldLoad; - } - - WebBrowserComponent& owner; - -private: - class ConnectionThread : private Thread - { - public: - struct Result - { - String url; - int statusCode = 0; - String description; - String data; - }; - - ConnectionThread (Pimpl& ownerToUse, - URL& url, - const StringArray& headers) - : Thread ("WebBrowserComponent::Pimpl::ConnectionThread"), - owner (ownerToUse), - webInputStream (new WebInputStream (url, true)) - { - webInputStream->withExtraHeaders (headers.joinIntoString ("\n")); - webInputStream->withConnectionTimeout (10000); - - result.url = url.toString (true); - - startThread(); - } - - ~ConnectionThread() override - { - webInputStream->cancel(); - signalThreadShouldExit(); - waitForThreadToExit (10000); - - webInputStream = nullptr; - } - - void run() override - { - if (! webInputStream->connect (nullptr)) - { - result.description = "Could not establish connection"; - owner.triggerAsyncUpdate(); - return; - } - - result.statusCode = webInputStream->getStatusCode(); - result.description = "Status code: " + String (result.statusCode); - readFromInputStream(); - owner.triggerAsyncUpdate(); - } - - const Result& getResult() { return result; } - - private: - void readFromInputStream() - { - MemoryOutputStream ostream; - - for (;;) - { - if (threadShouldExit()) - return; - - char buffer [8192]; - auto num = webInputStream->read (buffer, sizeof (buffer)); - - if (num <= 0) - break; - - ostream.write (buffer, (size_t) num); - } - - result.data = ostream.toUTF8(); - } - - Pimpl& owner; - std::unique_ptr webInputStream; - 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/rmsl/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/rmsl/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)); - - 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/rmsl/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 != nullptr) - { - jmethodID method = env->GetMethodID (responseClass, "getReasonPhrase", "()Ljava/lang/String;"); - - if (method != nullptr) - { - 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; - WaitableEvent responseReadyEvent; - - WeakReference::Master masterReference; - friend class WeakReference; -}; - -//============================================================================== -WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden) - : blankPageShown (false), - unloadPageWhenHidden (unloadWhenHidden) -{ - setOpaque (true); - - browser.reset (new Pimpl (*this)); - addAndMakeVisible (browser.get()); -} - -WebBrowserComponent::~WebBrowserComponent() -{ -} - -//============================================================================== -void WebBrowserComponent::goToURL (const String& url, - const StringArray* headers, - const MemoryBlock* postData) -{ - lastURL = url; - - if (headers != nullptr) - lastHeaders = *headers; - else - lastHeaders.clear(); - - if (postData != nullptr) - lastPostData = *postData; - else - lastPostData.reset(); - - blankPageShown = false; - - browser->goToURL (url, headers, postData); -} - -void WebBrowserComponent::stop() -{ - browser->stop(); -} - -void WebBrowserComponent::goBack() -{ - browser->goBack(); - - lastURL.clear(); - blankPageShown = false; -} - -void WebBrowserComponent::goForward() -{ - lastURL.clear(); - - browser->goForward(); -} - -void WebBrowserComponent::refresh() -{ - browser->refresh(); -} - -//============================================================================== -void WebBrowserComponent::paint (Graphics& g) -{ - g.fillAll (Colours::white); -} - -void WebBrowserComponent::checkWindowAssociation() -{ - if (isShowing()) - { - if (blankPageShown) - goBack(); - } - else - { - if (unloadPageWhenHidden && ! blankPageShown) - { - // when the component becomes invisible, some stuff like flash - // carries on playing audio, so we need to force it onto a blank - // page to avoid this, (and send it back when it's made visible again). - - blankPageShown = true; - browser->goToURL ("about:blank", nullptr, nullptr); - } - } -} - -void WebBrowserComponent::reloadLastURL() -{ - if (lastURL.isNotEmpty()) - { - goToURL (lastURL, &lastHeaders, lastPostData.isEmpty() ? nullptr : &lastPostData); - lastURL.clear(); - } -} - -void WebBrowserComponent::parentHierarchyChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::resized() -{ - browser->setSize (getWidth(), getHeight()); -} - -void WebBrowserComponent::visibilityChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::focusGained (FocusChangeType) -{ -} - -void WebBrowserComponent::clearCookies() -{ - auto* env = getEnv(); - - auto cookieManager = LocalRef (env->CallStaticObjectMethod (AndroidCookieManager, - AndroidCookieManager.getInstance)); - - jmethodID clearCookiesMethod = nullptr; - - if (getAndroidSDKVersion() >= 21) - { - clearCookiesMethod = env->GetMethodID (AndroidCookieManager, "removeAllCookies", "(Landroid/webkit/ValueCallback;)V"); - env->CallVoidMethod (cookieManager, clearCookiesMethod, 0); - } - else - { - clearCookiesMethod = env->GetMethodID (AndroidCookieManager, "removeAllCookie", "()V"); - env->CallVoidMethod (cookieManager, clearCookiesMethod); - } -} - -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/source/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp b/source/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp deleted file mode 100644 index 05c25dc75..000000000 --- a/source/modules/juce_gui_extra/native/juce_ios_PushNotifications.cpp +++ /dev/null @@ -1,998 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -namespace PushNotificationsDelegateDetails -{ - //============================================================================== - using Action = PushNotifications::Settings::Action; - using Category = PushNotifications::Settings::Category; - - void* actionToNSAction (const Action& a, bool iOSEarlierThan10) - { - if (iOSEarlierThan10) - { - auto action = [[UIMutableUserNotificationAction alloc] init]; - - action.identifier = juceStringToNS (a.identifier); - action.title = juceStringToNS (a.title); - action.behavior = a.style == Action::text ? UIUserNotificationActionBehaviorTextInput - : UIUserNotificationActionBehaviorDefault; - action.parameters = varObjectToNSDictionary (a.parameters); - action.activationMode = a.triggerInBackground ? UIUserNotificationActivationModeBackground - : UIUserNotificationActivationModeForeground; - action.destructive = (bool) a.destructive; - - [action autorelease]; - - return action; - } - else - { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - if (a.style == Action::text) - { - return [UNTextInputNotificationAction actionWithIdentifier: juceStringToNS (a.identifier) - title: juceStringToNS (a.title) - options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2) - textInputButtonTitle: juceStringToNS (a.textInputButtonText) - textInputPlaceholder: juceStringToNS (a.textInputPlaceholder)]; - } - - return [UNNotificationAction actionWithIdentifier: juceStringToNS (a.identifier) - title: juceStringToNS (a.title) - options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)]; - #else - return nullptr; - #endif - } - } - - void* categoryToNSCategory (const Category& c, bool iOSEarlierThan10) - { - if (iOSEarlierThan10) - { - auto category = [[UIMutableUserNotificationCategory alloc] init]; - category.identifier = juceStringToNS (c.identifier); - - auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()]; - - for (const auto& a : c.actions) - { - auto* action = (UIUserNotificationAction*) actionToNSAction (a, iOSEarlierThan10); - [actions addObject: action]; - } - - [category setActions: actions forContext: UIUserNotificationActionContextDefault]; - [category setActions: actions forContext: UIUserNotificationActionContextMinimal]; - - [category autorelease]; - - return category; - } - else - { - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - auto actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()]; - - for (const auto& a : c.actions) - { - auto* action = (UNNotificationAction*) actionToNSAction (a, iOSEarlierThan10); - [actions addObject: action]; - } - - return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier) - actions: actions - intentIdentifiers: @[] - options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0]; - #else - return nullptr; - #endif - } - } - - //============================================================================== - UILocalNotification* juceNotificationToUILocalNotification (const PushNotifications::Notification& n) - { - auto notification = [[UILocalNotification alloc] init]; - - notification.alertTitle = juceStringToNS (n.title); - notification.alertBody = juceStringToNS (n.body); - notification.category = juceStringToNS (n.category); - notification.applicationIconBadgeNumber = n.badgeNumber; - - auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec); - notification.fireDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.]; - notification.userInfo = varObjectToNSDictionary (n.properties); - - auto soundToPlayString = n.soundToPlay.toString (true); - - if (soundToPlayString == "default_os_sound") - notification.soundName = UILocalNotificationDefaultSoundName; - else if (soundToPlayString.isNotEmpty()) - notification.soundName = juceStringToNS (soundToPlayString); - - return notification; - } - - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - UNNotificationRequest* juceNotificationToUNNotificationRequest (const PushNotifications::Notification& n) - { - // content - auto content = [[UNMutableNotificationContent alloc] init]; - - content.title = juceStringToNS (n.title); - content.subtitle = juceStringToNS (n.subtitle); - content.threadIdentifier = juceStringToNS (n.groupId); - content.body = juceStringToNS (n.body); - content.categoryIdentifier = juceStringToNS (n.category); - content.badge = [NSNumber numberWithInt: n.badgeNumber]; - - auto soundToPlayString = n.soundToPlay.toString (true); - - if (soundToPlayString == "default_os_sound") - content.sound = [UNNotificationSound defaultSound]; - else if (soundToPlayString.isNotEmpty()) - content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)]; - - auto* propsDict = (NSMutableDictionary*) varObjectToNSDictionary (n.properties); - [propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")]; - content.userInfo = propsDict; - - // trigger - UNTimeIntervalNotificationTrigger* trigger = nil; - - if (std::abs (n.triggerIntervalSec) >= 0.001) - { - BOOL shouldRepeat = n.repeat && n.triggerIntervalSec >= 60; - trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: n.triggerIntervalSec repeats: shouldRepeat]; - } - - // request - // each notification on iOS 10 needs to have an identifier, otherwise it will not show up - jassert (n.identifier.isNotEmpty()); - UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier: juceStringToNS (n.identifier) - content: content - trigger: trigger]; - - [content autorelease]; - - return request; - } - #endif - - String getUserResponseFromNSDictionary (NSDictionary* dictionary) - { - if (dictionary == nil || dictionary.count == 0) - return {}; - - jassert (dictionary.count == 1); - - for (NSString* key in dictionary) - { - const auto keyString = nsStringToJuce (key); - - id value = dictionary[key]; - - if ([value isKindOfClass: [NSString class]]) - return nsStringToJuce ((NSString*) value); - } - - jassertfalse; - return {}; - } - - //============================================================================== - var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar) - { - DynamicObject* dictionaryVarObject = dictionaryVar.getDynamicObject(); - - if (dictionaryVarObject == nullptr) - return {}; - - const auto& properties = dictionaryVarObject->getProperties(); - - DynamicObject::Ptr propsVarObject = new DynamicObject(); - - for (int i = 0; i < properties.size(); ++i) - { - auto propertyName = properties.getName (i).toString(); - - if (propertyName == "aps") - continue; - - propsVarObject->setProperty (propertyName, properties.getValueAt (i)); - } - - return var (propsVarObject.get()); - } - - //============================================================================== - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - double getIntervalSecFromUNNotificationTrigger (UNNotificationTrigger* t) - { - if (t != nil) - { - if ([t isKindOfClass: [UNTimeIntervalNotificationTrigger class]]) - { - auto* trigger = (UNTimeIntervalNotificationTrigger*) t; - return trigger.timeInterval; - } - else if ([t isKindOfClass: [UNCalendarNotificationTrigger class]]) - { - auto* trigger = (UNCalendarNotificationTrigger*) t; - NSDate* date = [trigger.dateComponents date]; - NSDate* dateNow = [NSDate date]; - return [dateNow timeIntervalSinceDate: date]; - } - } - - return 0.; - } - - PushNotifications::Notification unNotificationRequestToJuceNotification (UNNotificationRequest* r) - { - PushNotifications::Notification n; - - n.identifier = nsStringToJuce (r.identifier); - n.title = nsStringToJuce (r.content.title); - n.subtitle = nsStringToJuce (r.content.subtitle); - n.body = nsStringToJuce (r.content.body); - n.groupId = nsStringToJuce (r.content.threadIdentifier); - n.category = nsStringToJuce (r.content.categoryIdentifier); - n.badgeNumber = r.content.badge.intValue; - - auto userInfoVar = nsDictionaryToVar (r.content.userInfo); - - if (auto* object = userInfoVar.getDynamicObject()) - { - static const Identifier soundName ("com.juce.soundName"); - n.soundToPlay = URL (object->getProperty (soundName).toString()); - object->removeProperty (soundName); - } - - n.properties = userInfoVar; - - n.triggerIntervalSec = getIntervalSecFromUNNotificationTrigger (r.trigger); - n.repeat = r.trigger != nil && r.trigger.repeats; - - return n; - } - - PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n) - { - return unNotificationRequestToJuceNotification (n.request); - } - #endif - - PushNotifications::Notification uiLocalNotificationToJuceNotification (UILocalNotification* n) - { - PushNotifications::Notification notif; - - notif.title = nsStringToJuce (n.alertTitle); - notif.body = nsStringToJuce (n.alertBody); - - if (n.fireDate != nil) - { - NSDate* dateNow = [NSDate date]; - NSDate* fireDate = n.fireDate; - - notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: fireDate]; - } - - notif.soundToPlay = URL (nsStringToJuce (n.soundName)); - notif.badgeNumber = (int) n.applicationIconBadgeNumber; - notif.category = nsStringToJuce (n.category); - notif.properties = nsDictionaryToVar (n.userInfo); - - return notif; - } - - Action uiUserNotificationActionToAction (UIUserNotificationAction* a) - { - Action action; - - action.identifier = nsStringToJuce (a.identifier); - action.title = nsStringToJuce (a.title); - action.style = a.behavior == UIUserNotificationActionBehaviorTextInput - ? Action::text - : Action::button; - - action.triggerInBackground = a.activationMode == UIUserNotificationActivationModeBackground; - action.destructive = a.destructive; - action.parameters = nsDictionaryToVar (a.parameters); - - return action; - } - - Category uiUserNotificationCategoryToCategory (UIUserNotificationCategory* c) - { - Category category; - category.identifier = nsStringToJuce (c.identifier); - - for (UIUserNotificationAction* a in [c actionsForContext: UIUserNotificationActionContextDefault]) - category.actions.add (uiUserNotificationActionToAction (a)); - - return category; - } - - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - Action unNotificationActionToAction (UNNotificationAction* a) - { - Action action; - - action.identifier = nsStringToJuce (a.identifier); - action.title = nsStringToJuce (a.title); - action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground); - action.destructive = a.options & UNNotificationActionOptionDestructive; - - if ([a isKindOfClass: [UNTextInputNotificationAction class]]) - { - auto* textAction = (UNTextInputNotificationAction*)a; - - action.style = Action::text; - action.textInputButtonText = nsStringToJuce (textAction.textInputButtonTitle); - action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder); - } - else - { - action.style = Action::button; - } - - return action; - } - - Category unNotificationCategoryToCategory (UNNotificationCategory* c) - { - Category category; - - category.identifier = nsStringToJuce (c.identifier); - category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction; - - for (UNNotificationAction* a in c.actions) - category.actions.add (unNotificationActionToAction (a)); - - return category; - } - #endif - - PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary) - { - const var dictionaryVar = nsDictionaryToVar (dictionary); - - const var apsVar = dictionaryVar.getProperty ("aps", {}); - - if (! apsVar.isObject()) - return {}; - - var alertVar = apsVar.getProperty ("alert", {}); - - const var titleVar = alertVar.getProperty ("title", {}); - const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar; - - const var categoryVar = apsVar.getProperty ("category", {}); - const var soundVar = apsVar.getProperty ("sound", {}); - const var badgeVar = apsVar.getProperty ("badge", {}); - const var threadIdVar = apsVar.getProperty ("thread-id", {}); - - PushNotifications::Notification notification; - - notification.title = titleVar .toString(); - notification.body = bodyVar .toString(); - notification.groupId = threadIdVar.toString(); - notification.category = categoryVar.toString(); - notification.soundToPlay = URL (soundVar.toString()); - notification.badgeNumber = (int) badgeVar; - notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar); - - return notification; - } -} - -//============================================================================== -struct PushNotificationsDelegate -{ - PushNotificationsDelegate() : delegate ([getClass().createInstance() init]) - { - Class::setThis (delegate.get(), this); - - id appDelegate = [[UIApplication sharedApplication] delegate]; - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegateToUse:)]) - [appDelegate performSelector: @selector (setPushNotificationsDelegateToUse:) withObject: delegate.get()]; - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - } - - virtual ~PushNotificationsDelegate() {} - - virtual void didRegisterUserNotificationSettings (UIUserNotificationSettings* notificationSettings) = 0; - - virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0; - - virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0; - - virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0; - - virtual void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo, - void (^completionHandler)(UIBackgroundFetchResult result)) = 0; - - virtual void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier, - NSDictionary* userInfo, - NSDictionary* responseInfo, - void (^completionHandler)()) = 0; - - virtual void didReceiveLocalNotification (UILocalNotification* notification) = 0; - - virtual void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier, - UILocalNotification* notification, - void (^completionHandler)()) = 0; - - virtual void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier, - UILocalNotification* notification, - NSDictionary* responseInfo, - void (^completionHandler)()) = 0; - - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - virtual void willPresentNotificationWithCompletionHandler (UNNotification* notification, - void (^completionHandler)(UNNotificationPresentationOptions options)) = 0; - - virtual void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response, - void (^completionHandler)()) = 0; - #endif - -protected: - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - NSUniquePtr> delegate; - #else - NSUniquePtr> delegate; - #endif - -private: - //============================================================================== - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - struct Class : public ObjCClass> - { - Class() : ObjCClass> ("JucePushNotificationsDelegate_") - #else - struct Class : public ObjCClass> - { - Class() : ObjCClass> ("JucePushNotificationsDelegate_") - #endif - { - addIvar ("self"); - - addMethod (@selector (application:didRegisterUserNotificationSettings:), didRegisterUserNotificationSettings); - addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications); - addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications); - addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification); - addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), didReceiveRemoteNotificationFetchCompletionHandler); - addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), handleActionForRemoteNotificationCompletionHandler); - addMethod (@selector (application:didReceiveLocalNotification:), didReceiveLocalNotification); - addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), handleActionForLocalNotificationCompletionHandler); - addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), handleActionForLocalNotificationWithResponseCompletionHandler); - - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), willPresentNotificationWithCompletionHandler); - addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), didReceiveNotificationResponseWithCompletionHandler); - #endif - - registerClass(); - } - - //============================================================================== - static PushNotificationsDelegate& getThis (id self) { return *getIvar (self, "self"); } - static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); } - - //============================================================================== - static void didRegisterUserNotificationSettings (id self, SEL, UIApplication*, - UIUserNotificationSettings* settings) { getThis (self).didRegisterUserNotificationSettings (settings); } - static void registeredForRemoteNotifications (id self, SEL, UIApplication*, - NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); } - - static void failedToRegisterForRemoteNotifications (id self, SEL, UIApplication*, - NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); } - - static void didReceiveRemoteNotification (id self, SEL, UIApplication*, - NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); } - - static void didReceiveRemoteNotificationFetchCompletionHandler (id self, SEL, UIApplication*, - NSDictionary* userInfo, - void (^completionHandler)(UIBackgroundFetchResult result)) { getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler); } - - static void handleActionForRemoteNotificationCompletionHandler (id self, SEL, UIApplication*, - NSString* actionIdentifier, - NSDictionary* userInfo, - NSDictionary* responseInfo, - void (^completionHandler)()) { getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler); } - - static void didReceiveLocalNotification (id self, SEL, UIApplication*, - UILocalNotification* notification) { getThis (self).didReceiveLocalNotification (notification); } - - static void handleActionForLocalNotificationCompletionHandler (id self, SEL, UIApplication*, - NSString* actionIdentifier, - UILocalNotification* notification, - void (^completionHandler)()) { getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler); } - - static void handleActionForLocalNotificationWithResponseCompletionHandler (id self, SEL, UIApplication*, - NSString* actionIdentifier, - UILocalNotification* notification, - NSDictionary* responseInfo, - void (^completionHandler)()) { getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler); } - - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - static void willPresentNotificationWithCompletionHandler (id self, SEL, UNUserNotificationCenter*, - UNNotification* notification, - void (^completionHandler)(UNNotificationPresentationOptions options)) { getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler); } - - static void didReceiveNotificationResponseWithCompletionHandler (id self, SEL, UNUserNotificationCenter*, - UNNotificationResponse* response, - void (^completionHandler)()) { getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler); } - #endif - }; - - //============================================================================== - static Class& getClass() - { - static Class c; - return c; - } -}; - -//============================================================================== -bool PushNotifications::Notification::isValid() const noexcept -{ - const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max; - - if (iOSEarlierThan10) - return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty(); - - return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty(); -} - -//============================================================================== -struct PushNotifications::Pimpl : private PushNotificationsDelegate -{ - Pimpl (PushNotifications& p) - : owner (p) - { - } - - void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse) - { - settings = settingsToUse; - - auto categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()]; - - if (iOSEarlierThan10) - { - for (const auto& c : settings.categories) - { - auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10); - [categories addObject: category]; - } - - UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0 - | (bool)settings.allowSound << 1 - | (bool)settings.allowAlert << 2); - - UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories]; - [[UIApplication sharedApplication] registerUserNotificationSettings: s]; - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - for (const auto& c : settings.categories) - { - auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10); - [categories addObject: category]; - } - - UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0 - | (bool)settings.allowSound << 1 - | (bool)settings.allowAlert << 2); - - [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories]; - [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions - completionHandler: ^(BOOL /*granted*/, NSError* /*error*/) - { - requestSettingsUsed(); - }]; - } - #endif - - [[UIApplication sharedApplication] registerForRemoteNotifications]; - } - - void requestSettingsUsed() - { - if (iOSEarlierThan10) - { - UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings; - - settings.allowBadge = s.types & UIUserNotificationTypeBadge; - settings.allowSound = s.types & UIUserNotificationTypeSound; - settings.allowAlert = s.types & UIUserNotificationTypeAlert; - - for (UIUserNotificationCategory *c in s.categories) - settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c)); - - owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); }); - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - - [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler: - ^(UNNotificationSettings* s) - { - [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler: - ^(NSSet* categories) - { - settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled; - settings.allowSound = s.soundSetting == UNNotificationSettingEnabled; - settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled; - - for (UNNotificationCategory* c in categories) - settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c)); - - owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); }); - } - ]; - - }]; - } - #endif - } - - bool areNotificationsEnabled() const { return true; } - - void sendLocalNotification (const Notification& n) - { - if (iOSEarlierThan10) - { - auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n); - - [[UIApplication sharedApplication] scheduleLocalNotification: notification]; - [notification release]; - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - - UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n); - - [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request - withCompletionHandler: ^(NSError* error) - { - jassert (error == nil); - - if (error != nil) - NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error); - }]; - } - #endif - } - - void getDeliveredNotifications() const - { - if (iOSEarlierThan10) - { - // Not supported on this platform - jassertfalse; - owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); }); - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler: - ^(NSArray* notifications) - { - Array notifs; - - for (UNNotification* n in notifications) - notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n)); - - owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); }); - }]; - } - #endif - } - - void removeAllDeliveredNotifications() - { - if (iOSEarlierThan10) - { - // Not supported on this platform - jassertfalse; - } - else - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - { - - [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications]; - } - #endif - } - - void removeDeliveredNotification (const String& identifier) - { - if (iOSEarlierThan10) - { - ignoreUnused (identifier); - // Not supported on this platform - jassertfalse; - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - - NSArray* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)]; - - [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers]; - } - #endif - } - - void setupChannels (const Array& groups, const Array& channels) - { - ignoreUnused (groups, channels); - } - - void getPendingLocalNotifications() const - { - if (iOSEarlierThan10) - { - Array notifs; - - for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications) - notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n)); - - owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); }); - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - - [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler: - ^(NSArray* requests) - { - Array notifs; - - for (UNNotificationRequest* r : requests) - notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r)); - - owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); }); - } - ]; - } - #endif - } - - void removePendingLocalNotification (const String& identifier) - { - if (iOSEarlierThan10) - { - // Not supported on this platform - jassertfalse; - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - - NSArray* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)]; - - [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers]; - } - #endif - } - - void removeAllPendingLocalNotifications() - { - if (iOSEarlierThan10) - { - [[UIApplication sharedApplication] cancelAllLocalNotifications]; - } - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - else - { - [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests]; - } - #endif - } - - String getDeviceToken() - { - // You need to call requestPermissionsWithSettings() first. - jassert (initialised); - - return deviceToken; - } - - //============================================================================== - //PushNotificationsDelegate - void didRegisterUserNotificationSettings (UIUserNotificationSettings*) override - { - requestSettingsUsed(); - } - - void registeredForRemoteNotifications (NSData* deviceTokenToUse) override - { - deviceToken = [deviceTokenToUse]() -> String - { - auto length = deviceTokenToUse.length; - - if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes) - { - NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)]; - - for (NSUInteger i = 0; i < length; ++i) - [hexString appendFormat:@"%02x", buffer[i]]; - - return nsStringToJuce ([hexString copy]); - } - - return {}; - }(); - - initialised = true; - - owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); }); - } - - void failedToRegisterForRemoteNotifications (NSError* error) override - { - ignoreUnused (error); - - deviceToken.clear(); - } - - void didReceiveRemoteNotification (NSDictionary* userInfo) override - { - auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo); - - owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); }); - } - - void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo, - void (^completionHandler)(UIBackgroundFetchResult result)) override - { - didReceiveRemoteNotification (userInfo); - completionHandler (UIBackgroundFetchResultNewData); - } - - void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier, - NSDictionary* userInfo, - NSDictionary* responseInfo, - void (^completionHandler)()) override - { - auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo); - auto actionString = nsStringToJuce (actionIdentifier); - auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo); - - owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); }); - - completionHandler(); - } - - void didReceiveLocalNotification (UILocalNotification* notification) override - { - auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification); - - owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); }); - } - - void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier, - UILocalNotification* notification, - void (^completionHandler)()) override - { - handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, - notification, - nil, - completionHandler); - } - - void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier, - UILocalNotification* notification, - NSDictionary* responseInfo, - void (^completionHandler)()) override - { - auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification); - auto actionString = nsStringToJuce (actionIdentifier); - auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo); - - owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, n, actionString, response); }); - - completionHandler(); - } - - #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - void willPresentNotificationWithCompletionHandler (UNNotification* notification, - void (^completionHandler)(UNNotificationPresentationOptions options)) override - { - NSUInteger options = NSUInteger ((int)settings.allowBadge << 0 - | (int)settings.allowSound << 1 - | (int)settings.allowAlert << 2); - - ignoreUnused (notification); - - completionHandler (options); - } - - void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response, - void (^completionHandler)()) override - { - const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]]; - - auto actionString = nsStringToJuce (response.actionIdentifier); - - if (actionString == "com.apple.UNNotificationDefaultActionIdentifier") - actionString.clear(); - else if (actionString == "com.apple.UNNotificationDismissActionIdentifier") - actionString = "com.juce.NotificationDeleted"; - - auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification); - - String responseString; - - if ([response isKindOfClass: [UNTextInputNotificationResponse class]]) - { - UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response; - responseString = nsStringToJuce (textResponse.userText); - } - - owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); }); - completionHandler(); - } - #endif - - void subscribeToTopic (const String& topic) { ignoreUnused (topic); } - void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); } - - void sendUpstreamMessage (const String& serverSenderId, - const String& collapseKey, - const String& messageId, - const String& messageType, - int timeToLive, - const StringPairArray& additionalData) - { - ignoreUnused (serverSenderId, collapseKey, messageId, messageType); - ignoreUnused (timeToLive, additionalData); - } - -private: - PushNotifications& owner; - - const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max; - - bool initialised = false; - String deviceToken; - - PushNotifications::Settings settings; -}; - -} // namespace juce diff --git a/source/modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm b/source/modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm deleted file mode 100644 index b1c373162..000000000 --- a/source/modules/juce_gui_extra/native/juce_ios_UIViewComponent.mm +++ /dev/null @@ -1,132 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2022 - Raw Material Software Limited - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 7 End-User License - Agreement and JUCE Privacy Policy. - - End User License Agreement: www.juce.com/juce-7-licence - Privacy Policy: www.juce.com/juce-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - 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 -{ - -class UIViewComponent::Pimpl : public ComponentMovementWatcher -{ -public: - Pimpl (UIView* v, Component& comp) - : ComponentMovementWatcher (&comp), - view (v), - owner (comp) - { - [view retain]; - - if (owner.isShowing()) - componentPeerChanged(); - } - - ~Pimpl() override - { - [view removeFromSuperview]; - [view release]; - } - - void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override - { - auto* topComp = owner.getTopLevelComponent(); - - if (topComp->getPeer() != nullptr) - { - auto pos = topComp->getLocalPoint (&owner, Point()); - - [view setFrame: CGRectMake ((float) pos.x, (float) pos.y, - (float) owner.getWidth(), (float) owner.getHeight())]; - } - } - - void componentPeerChanged() override - { - auto* peer = owner.getPeer(); - - if (currentPeer != peer) - { - if ([view superview] != nil) - [view removeFromSuperview]; // Must be careful not to call this unless it's required - e.g. some Apple AU views - // override the call and use it as a sign that they're being deleted, which breaks everything.. - currentPeer = peer; - - if (peer != nullptr) - { - UIView* peerView = (UIView*) peer->getNativeHandle(); - [peerView addSubview: view]; - componentMovedOrResized (false, false); - } - } - - [view setHidden: ! owner.isShowing()]; - } - - void componentVisibilityChanged() override - { - componentPeerChanged(); - } - - Rectangle getViewBounds() const - { - CGRect r = [view frame]; - return Rectangle ((int) r.size.width, (int) r.size.height); - } - - UIView* const view; - -private: - Component& owner; - ComponentPeer* currentPeer = nullptr; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) -}; - -//============================================================================== -UIViewComponent::UIViewComponent() {} -UIViewComponent::~UIViewComponent() {} - -void UIViewComponent::setView (void* view) -{ - if (view != getView()) - { - pimpl.reset(); - - if (view != nullptr) - pimpl.reset (new Pimpl ((UIView*) view, *this)); - } -} - -void* UIViewComponent::getView() const -{ - return pimpl == nullptr ? nullptr : pimpl->view; -} - -void UIViewComponent::resizeToFitView() -{ - if (pimpl != nullptr) - setBounds (pimpl->getViewBounds()); -} - -void UIViewComponent::paint (Graphics&) {} - -} // namespace juce