Hi there. Here is a little demo app which shows how displaying the MIDI Bluetooth pairing dialogue breaks the safe area insets / kiosk mode on Android. Toggling between normal and kiosk mode works fine until the dialogue is displayed.
Note that not only the Bluetooth dialogue has this effect, but likewise other windows which are placed on the desktop in a similar manner. Also, the pairing dialogue is not sized correctly when the app is in pop-up mode.
(Very sorry to continue to torture you with this, it must be so frustrating!)
#include <JuceHeader.h>
class MainComponent : public juce::Component
{
public:
MainComponent()
{
addAndMakeVisible (kioskButton);
kioskButton.setButtonText ("Toggle kiosk mode");
kioskButton.onClick = [this] { toggleKioskMode(); };
addAndMakeVisible (styleButton);
styleButton.setButtonText ("Toggle app style");
styleButton.onClick = [this] { toggleAppStyle(); };
addAndMakeVisible (bluetoothButton);
bluetoothButton.setButtonText ("Bluetooth MIDI pairing dialog");
bluetoothButton.onClick = [this]
{
if (juce::BluetoothMidiDevicePairingDialogue::isAvailable())
openBluetoothDialogue();
};
}
void openBluetoothDialogue()
{
if (juce::RuntimePermissions::isGranted (juce::RuntimePermissions::bluetoothMidi))
{
juce::BluetoothMidiDevicePairingDialogue::open();
}
else
{
juce::RuntimePermissions::request (juce::RuntimePermissions::bluetoothMidi, [&] (auto)
{
if (juce::RuntimePermissions::isGranted (juce::RuntimePermissions::bluetoothMidi))
juce::BluetoothMidiDevicePairingDialogue::open();
});
}
}
void resized() override
{
auto area = getSafeBounds();
const int buttonHeight = 40;
const int spacing = 10;
const int totalHeight = 3 * buttonHeight + 2 * spacing;
auto y = (area.getHeight() - totalHeight) / 2;
auto buttonArea = area.withTrimmedTop (y).removeFromTop (totalHeight).reduced (20, 0);
kioskButton.setBounds (buttonArea.removeFromTop (buttonHeight));
buttonArea.removeFromTop (spacing);
styleButton.setBounds (buttonArea.removeFromTop (buttonHeight));
buttonArea.removeFromTop (spacing);
bluetoothButton.setBounds (buttonArea.removeFromTop (buttonHeight));
repaint();
}
juce::Rectangle<int> getSafeBounds()
{
auto* display = juce::Desktop::getInstance().getDisplays().getDisplayForRect (getScreenBounds());
auto safeBounds = getLocalBounds();
#if JUCE_IOS || JUCE_ANDROID
display->keyboardInsets.subtractFrom (safeBounds);
display->safeAreaInsets.subtractFrom (safeBounds);
#endif
return safeBounds;
}
void paint (juce::Graphics& g) override
{
auto* display = juce::Desktop::getInstance().getDisplays().getDisplayForRect (getScreenBounds());
auto safeBounds = getSafeBounds();
juce::String displayInfo ("totalArea: " + display->totalArea.toString() + juce::newLine +
"userArea: " + display->userArea.toString() + juce::newLine +
"safeBounds: " + safeBounds.toString() + juce::newLine +
"kiosk mode: " + (isKioskMode() ? "on" : "off") + juce::newLine +
"app style: " + (isDarkStyle() ? "dark" : "light"));
g.fillAll (isDarkStyle() ? juce::Colours::darkblue : juce::Colours::lightblue);
g.setColour (isDarkStyle() ? juce::Colours::darkgreen : juce::Colours::lightgreen);
g.fillRect (safeBounds);
g.setColour (juce::Colours::red);
g.drawRect (safeBounds.reduced (4), 3.0f);
g.setColour (isDarkStyle() ? juce::Colours::white : juce::Colours::black);
g.drawFittedText (displayInfo, safeBounds.reduced (10), juce::Justification::topLeft, 30);
}
bool isKioskMode()
{
return juce::Desktop::getInstance().getKioskModeComponent() != nullptr;
}
void toggleKioskMode()
{
juce::Desktop::getInstance().setKioskModeComponent (isKioskMode() ? nullptr : getTopLevelComponent());
if (auto* peer = getPeer())
peer->setFullScreen (true);
}
bool isDarkStyle()
{
if (auto* peer = getPeer())
return peer->getAppStyle() == juce::ComponentPeer::Style::dark;
return false;
}
void toggleAppStyle()
{
if (auto* peer = getPeer())
peer->setAppStyle (isDarkStyle() ? juce::ComponentPeer::Style::light
: juce::ComponentPeer::Style::dark);
repaint();
}
private:
juce::TextButton kioskButton;
juce::TextButton styleButton;
juce::TextButton bluetoothButton;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};
class NewProjectApplication : public juce::JUCEApplication
{
public:
NewProjectApplication() {}
const juce::String getApplicationName() override { return ProjectInfo::projectName; }
const juce::String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return false; }
void initialise (const juce::String& commandLine) override { mainWindow.reset (new MainWindow (getApplicationName())); }
void shutdown() override { mainWindow = nullptr; }
void systemRequestedQuit() override { quit(); }
class MainWindow : public juce::DocumentWindow
{
public:
#if JUCE_ANDROID
juce::OpenGLContext openGLContext;
#endif
MainWindow (juce::String name)
: DocumentWindow (name, juce::Colours::grey, DocumentWindow::allButtons)
{
#if JUCE_ANDROID
openGLContext.attachTo (*this);
#endif
setUsingNativeTitleBar (true);
setContentOwned (new MainComponent(), false);
#if JUCE_IOS || JUCE_ANDROID
setFullScreen (true);
#else
centreWithSize (600, 400);
#endif
setResizable (true, true);
setVisible (true);
getPeer()->setAppStyle (juce::ComponentPeer::Style::dark);
}
void closeButtonPressed() override { JUCEApplication::getInstance()->systemRequestedQuit(); }
~MainWindow()
{
#if JUCE_ANDROID
openGLContext.detach();
#endif
clearContentComponent();
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};
private:
std::unique_ptr<MainWindow> mainWindow;
};
START_JUCE_APPLICATION (NewProjectApplication)
