diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h index f791d67080..e1dda241de 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h @@ -1003,8 +1003,53 @@ private: else { juceMidiCode = javaSourceFolder.getChildFile ("AndroidMidiFallback.java") - .loadFileAsString() - .replace ("JuceAppActivity", className); + .loadFileAsString() + .replace ("JuceAppActivity", className); + } + + String juceWebViewImports, juceWebViewCodeNative, juceWebViewCode; + + if (androidMinimumSDK.get().getIntValue() >= 23) + juceWebViewImports << "import android.webkit.WebResourceError;" << newLine; + + if (androidMinimumSDK.get().getIntValue() >= 21) + juceWebViewImports << "import android.webkit.WebResourceRequest;" << newLine; + + if (androidMinimumSDK.get().getIntValue() >= 11) + juceWebViewImports << "import android.webkit.WebResourceResponse;" << newLine; + + auto javaWebViewFile = javaSourceFolder.getChildFile ("AndroidWebView.java"); + auto juceWebViewCodeAll = javaWebViewFile.loadFileAsString(); + + if (androidMinimumSDK.get().getIntValue() <= 10) + { + juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi1_10", false, false) + .upToFirstOccurrenceOf ("WebViewApi1_10$$", false, false); + } + else + { + if (androidMinimumSDK.get().getIntValue() >= 23) + { + juceWebViewCodeNative << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewNativeApi23", false, false) + .upToFirstOccurrenceOf ("WebViewNativeApi23$$", false, false); + + juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi23", false, false) + .upToFirstOccurrenceOf ("WebViewApi23$$", false, false); + } + + if (androidMinimumSDK.get().getIntValue() >= 21) + { + juceWebViewCodeNative << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewNativeApi21", false, false) + .upToFirstOccurrenceOf ("WebViewNativeApi21$$", false, false); + + juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi21", false, false) + .upToFirstOccurrenceOf ("WebViewApi21$$", false, false); + } + else + { + juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi11_20", false, false) + .upToFirstOccurrenceOf ("WebViewApi11_20$$", false, false); + } } auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java"); @@ -1021,6 +1066,12 @@ private: newFile << juceMidiCode; else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$")) newFile << juceRuntimePermissionsCode; + else if (line.contains ("$$JuceAndroidWebViewImports$$")) + newFile << juceWebViewImports; + else if (line.contains ("$$JuceAndroidWebViewNativeCode$$")) + newFile << juceWebViewCodeNative; + else if (line.contains ("$$JuceAndroidWebViewCode$$")) + newFile << juceWebViewCode; else newFile << line.replace ("JuceAppActivity", className) .replace ("package com.juce;", "package " + package + ";") << newLine; diff --git a/modules/juce_core/native/java/AndroidWebView.java b/modules/juce_core/native/java/AndroidWebView.java new file mode 100644 index 0000000000..74d851ff70 --- /dev/null +++ b/modules/juce_core/native/java/AndroidWebView.java @@ -0,0 +1,69 @@ +$$WebViewNativeApi23 private native void webViewReceivedError (long host, WebView view, WebResourceRequest request, WebResourceError error);WebViewNativeApi23$$ +$$WebViewNativeApi21 private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse);WebViewNativeApi21$$ + +$$WebViewApi1_10 + @Override + public void onPageStarted (WebView view, String url, Bitmap favicon) + { + if (host != 0) + webViewPageLoadStarted (host, view, url); + } +WebViewApi1_10$$ + +$$WebViewApi11_20 + @Override + public WebResourceResponse shouldInterceptRequest (WebView view, String url) + { + synchronized (hostLock) + { + if (host != 0) + { + boolean shouldLoad = webViewPageLoadStarted (host, view, url); + + if (shouldLoad) + return null; + } + } + + return new WebResourceResponse ("text/html", null, null); + } +WebViewApi11_20$$ + +$$WebViewApi21 + @Override + public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) + { + synchronized (hostLock) + { + if (host != 0) + { + boolean shouldLoad = webViewPageLoadStarted (host, view, request.getUrl().toString()); + + if (shouldLoad) + return null; + } + } + + return new WebResourceResponse ("text/html", null, null); + } +WebViewApi21$$ + +$$WebViewApi23 + @Override + public void onReceivedError (WebView view, WebResourceRequest request, WebResourceError error) + { + if (host == 0) + return; + + webViewReceivedError (host, view, request, error); + } + + @Override + public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse) + { + if (host == 0) + return; + + webViewReceivedHttpError (host, view, request, errorResponse); + } +WebViewApi23$$ diff --git a/modules/juce_core/native/java/JuceAppActivity.java b/modules/juce_core/native/java/JuceAppActivity.java index 1f5206732e..4ae9f5e02a 100644 --- a/modules/juce_core/native/java/JuceAppActivity.java +++ b/modules/juce_core/native/java/JuceAppActivity.java @@ -30,10 +30,12 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.net.http.SslError; import android.net.Uri; import android.os.Bundle; import android.os.Looper; import android.os.Handler; +import android.os.Message; import android.os.ParcelUuid; import android.os.Environment; import android.view.*; @@ -47,6 +49,11 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; +$$JuceAndroidWebViewImports$$ // If you get an error here, you need to re-save your project with the Projucer! +import android.webkit.WebView; +import android.webkit.WebViewClient; import java.lang.Runnable; import java.lang.ref.WeakReference; import java.lang.reflect.*; @@ -1336,6 +1343,78 @@ public class JuceAppActivity extends Activity startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url))); } + private native boolean webViewPageLoadStarted (long host, WebView view, String url); + private native void webViewPageLoadFinished (long host, WebView view, String url); +$$JuceAndroidWebViewNativeCode$$ // If you get an error here, you need to re-save your project with the Projucer! + private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error); + private native void webViewCloseWindowRequest (long host, WebView view); + private native void webViewCreateWindowRequest (long host, WebView view); + + //============================================================================== + public class JuceWebViewClient extends WebViewClient + { + public JuceWebViewClient (long hostToUse) + { + host = hostToUse; + } + + public void hostDeleted() + { + synchronized (hostLock) + { + host = 0; + } + } + + @Override + public void onPageFinished (WebView view, String url) + { + if (host == 0) + return; + + webViewPageLoadFinished (host, view, url); + } + + @Override + public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) + { + if (host == 0) + return; + + webViewReceivedSslError (host, view, handler, error); + } + $$JuceAndroidWebViewCode$$ // If you get an error here, you need to re-save your project with the Projucer! + + private long host; + private final Object hostLock = new Object(); + } + + public class JuceWebChromeClient extends WebChromeClient + { + public JuceWebChromeClient (long hostToUse) + { + host = hostToUse; + } + + @Override + public void onCloseWindow (WebView window) + { + webViewCloseWindowRequest (host, window); + } + + @Override + public boolean onCreateWindow (WebView view, boolean isDialog, + boolean isUserGesture, Message resultMsg) + { + webViewCreateWindowRequest (host, view); + return false; + } + + private long host; + private final Object hostLock = new Object(); + } + + //============================================================================== public static final String getLocaleValue (boolean isRegion) { java.util.Locale locale = java.util.Locale.getDefault(); diff --git a/modules/juce_core/native/juce_android_JNIHelpers.h b/modules/juce_core/native/juce_android_JNIHelpers.h index 74f90f097c..1b21e6db47 100644 --- a/modules/juce_core/native/juce_android_JNIHelpers.h +++ b/modules/juce_core/native/juce_android_JNIHelpers.h @@ -421,6 +421,58 @@ DECLARE_JNI_CLASS (JavaObject, "java/lang/Object"); DECLARE_JNI_CLASS (Intent, "android/content/Intent"); #undef JNI_CLASS_MEMBERS +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + 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") + +DECLARE_JNI_CLASS (AndroidView, "android/view/View"); +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (addView, "addView", "(Landroid/view/View;)V") \ + METHOD (removeView, "removeView", "(Landroid/view/View;)V") + +DECLARE_JNI_CLASS (AndroidViewGroup, "android/view/ViewGroup") +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + 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) \ + 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) \ + 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) \ + METHOD (concat, "concat", "(Ljava/lang/String;)Ljava/lang/String;") \ + METHOD (getBytes, "getBytes", "()[B") + +DECLARE_JNI_CLASS (JavaString, "java/lang/String"); +#undef JNI_CLASS_MEMBERS + //============================================================================== class AndroidInterfaceImplementer; diff --git a/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 8b02e27445..d1bd290add 100644 --- a/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -187,22 +187,12 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (setViewName, "setViewName", "(Ljava/lang/String;)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 (bringToFront, "bringToFront", "()V") \ - METHOD (requestFocus, "requestFocus", "()Z") \ - METHOD (setVisible, "setVisible", "(Z)V") \ - METHOD (isVisible, "isVisible", "()Z") \ - METHOD (hasFocus, "hasFocus", "()Z") \ - METHOD (invalidate, "invalidate", "(IIII)V") \ - METHOD (containsPoint, "containsPoint", "(II)Z") \ - METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ - METHOD (setSystemUiVisibility, "setSystemUiVisibilityCompat", "(I)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") \ DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); #undef JNI_CLASS_MEMBERS @@ -302,7 +292,7 @@ public: if (MessageManager::getInstance()->isThisTheMessageThread()) { fullScreen = isNowFullScreen; - view.callVoidMethod (ComponentPeerView.layout, + view.callVoidMethod (AndroidView.layout, r.getX(), r.getY(), r.getRight(), r.getBottom()); } else @@ -314,7 +304,7 @@ public: void messageCallback() override { - view.callVoidMethod (ComponentPeerView.layout, + view.callVoidMethod (AndroidView.layout, bounds.getX(), bounds.getY(), bounds.getRight(), bounds.getBottom()); } @@ -329,10 +319,10 @@ public: Rectangle getBounds() const override { - return (Rectangle (view.callIntMethod (ComponentPeerView.getLeft), - view.callIntMethod (ComponentPeerView.getTop), - view.callIntMethod (ComponentPeerView.getWidth), - view.callIntMethod (ComponentPeerView.getHeight)) / scale).toNearestInt(); + return (Rectangle (view.callIntMethod (AndroidView.getLeft), + view.callIntMethod (AndroidView.getTop), + view.callIntMethod (AndroidView.getWidth), + view.callIntMethod (AndroidView.getHeight)) / scale).toNearestInt(); } void handleScreenSizeChange() override @@ -345,8 +335,8 @@ public: Point getScreenPosition() const { - return Point (view.callIntMethod (ComponentPeerView.getLeft), - view.callIntMethod (ComponentPeerView.getTop)) / scale; + return Point (view.callIntMethod (AndroidView.getLeft), + view.callIntMethod (AndroidView.getTop)) / scale; } Point localToGlobal (Point relativePosition) override @@ -393,7 +383,7 @@ public: SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 4096 }; - view.callVoidMethod (ComponentPeerView.setSystemUiVisibility, + view.callVoidMethod (ComponentPeerView.setSystemUiVisibilityCompat, hidden ? (jint) (SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_IMMERSIVE_STICKY) : (jint) (SYSTEM_UI_FLAG_VISIBLE)); @@ -471,7 +461,7 @@ public: // Avoid calling bringToFront excessively: it's very slow if (frontWindow != this) { - view.callVoidMethod (ComponentPeerView.bringToFront); + view.callVoidMethod (AndroidView.bringToFront); frontWindow = this; } @@ -547,7 +537,7 @@ public: bool isFocused() const override { if (view != nullptr) - return view.callBooleanMethod (ComponentPeerView.hasFocus); + return view.callBooleanMethod (AndroidView.hasFocus); return false; } @@ -555,7 +545,7 @@ public: void grabFocus() override { if (view != nullptr) - view.callBooleanMethod (ComponentPeerView.requestFocus); + view.callBooleanMethod (AndroidView.requestFocus); } void handleFocusChangeCallback (bool hasFocus) @@ -645,7 +635,7 @@ public: if (MessageManager::getInstance()->isThisTheMessageThread()) { - view.callVoidMethod (ComponentPeerView.invalidate, area.getX(), area.getY(), area.getRight(), area.getBottom()); + view.callVoidMethod (AndroidView.invalidate, area.getX(), area.getY(), area.getRight(), area.getBottom()); } else { @@ -656,7 +646,7 @@ public: void messageCallback() override { - view.callVoidMethod (ComponentPeerView.invalidate, area.getX(), area.getY(), + view.callVoidMethod (AndroidView.invalidate, area.getX(), area.getY(), area.getRight(), area.getBottom()); } diff --git a/modules/juce_gui_extra/embedding/juce_AndroidViewComponent.h b/modules/juce_gui_extra/embedding/juce_AndroidViewComponent.h new file mode 100644 index 0000000000..49bc6b4a12 --- /dev/null +++ b/modules/juce_gui_extra/embedding/juce_AndroidViewComponent.h @@ -0,0 +1,79 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 JUCE_ANDROID || DOXYGEN + +//============================================================================== +/** + An Android-specific class that can create and embed a View inside itself. + + To use it, create one of these, put it in place and make sure it's visible in a + window, then use setView() to assign a View to it. The view will then be + moved and resized to follow the movements of this component. + + Of course, since the view is a native object, it'll obliterate any + juce components that may overlap this component, but that's life. +*/ +class JUCE_API AndroidViewComponent : public Component +{ +public: + //============================================================================== + /** Create an initially-empty container. */ + AndroidViewComponent(); + + /** Destructor. */ + ~AndroidViewComponent(); + + /** Assigns a View to this peer. + + The view will be retained and released by this component for as long as + it is needed. To remove the current view, just call setView (nullptr). + */ + void setView (void* uiView); + + /** Returns the current View. */ + void* getView() const; + + /** Resizes this component to fit the view that it contains. */ + void resizeToFitView(); + + //============================================================================== + /** @internal */ + void paint (Graphics&) override; + +private: + class Pimpl; + ScopedPointer pimpl; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidViewComponent) +}; + +#endif + +} // namespace juce diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp index 70e8e349f3..f8693b5634 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -163,6 +163,8 @@ //============================================================================== #elif JUCE_ANDROID + #include "native/juce_AndroidViewComponent.cpp" + #if JUCE_WEB_BROWSER #include "native/juce_android_WebBrowserComponent.cpp" #endif diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h index 4f69a00a62..b24ab77540 100644 --- a/modules/juce_gui_extra/juce_gui_extra.h +++ b/modules/juce_gui_extra/juce_gui_extra.h @@ -83,6 +83,7 @@ #include "code_editor/juce_XMLCodeTokeniser.h" #include "code_editor/juce_LuaCodeTokeniser.h" #include "embedding/juce_ActiveXControlComponent.h" +#include "embedding/juce_AndroidViewComponent.h" #include "embedding/juce_NSViewComponent.h" #include "embedding/juce_UIViewComponent.h" #include "embedding/juce_XEmbedComponent.h" diff --git a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h index 67a055f82e..8c60615983 100644 --- a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h +++ b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h @@ -136,7 +136,7 @@ public: private: //============================================================================== class Pimpl; - Pimpl* browser; + ScopedPointer browser; bool blankPageShown, unloadPageWhenBrowserIsHidden; String lastURL; StringArray lastHeaders; @@ -145,6 +145,10 @@ private: void reloadLastURL(); void checkWindowAssociation(); + #if JUCE_ANDROID + friend bool juce_webViewPageLoadStarted (WebBrowserComponent*, const String&); + #endif + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebBrowserComponent) }; diff --git a/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp b/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp new file mode 100644 index 0000000000..03190d461e --- /dev/null +++ b/modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp @@ -0,0 +1,162 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-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 AndroidViewComponent::Pimpl : public ComponentMovementWatcher +{ +public: + Pimpl (jobject v, Component& comp) + : ComponentMovementWatcher (&comp), + view (v), + owner (comp) + { + if (owner.isShowing()) + componentPeerChanged(); + } + + ~Pimpl() + { + removeFromParent(); + } + + void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override + { + auto* topComp = owner.getTopLevelComponent(); + + if (topComp->getPeer() != nullptr) + { + auto pos = topComp->getLocalPoint (&owner, Point()); + + Rectangle r (pos.x, pos.y, owner.getWidth(), owner.getHeight()); + r *= Desktop::getInstance().getDisplays().getMainDisplay().scale; + + getEnv()->CallVoidMethod (view, AndroidView.layout, r.getX(), r.getY(), + r.getRight(), r.getBottom()); + } + } + + void componentPeerChanged() override + { + auto* peer = owner.getPeer(); + + if (currentPeer != peer) + { + removeFromParent(); + + currentPeer = peer; + + addToParent(); + } + + enum + { + VISIBLE = 0, + INVISIBLE = 4 + }; + + getEnv()->CallVoidMethod (view, AndroidView.setVisibility, owner.isShowing() ? VISIBLE : INVISIBLE); + } + + void componentVisibilityChanged() override + { + componentPeerChanged(); + } + + Rectangle getViewBounds() const + { + auto* env = getEnv(); + + int width = env->CallIntMethod (view, AndroidView.getWidth); + int height = env->CallIntMethod (view, AndroidView.getHeight); + + return Rectangle (width, height); + } + + GlobalRef view; + +private: + void addToParent() + { + if (currentPeer != nullptr) + { + jobject peerView = (jobject) currentPeer->getNativeHandle(); + + // Assuming a parent is always of ViewGroup type + getEnv()->CallVoidMethod (peerView, AndroidViewGroup.addView, view.get()); + + componentMovedOrResized (false, false); + } + } + + void removeFromParent() + { + auto* env = getEnv(); + auto parentView = env->CallObjectMethod (view, AndroidView.getParent); + + if (parentView != 0) + { + // Assuming a parent is always of ViewGroup type + env->CallVoidMethod (parentView, AndroidViewGroup.removeView, view.get()); + } + } + + Component& owner; + ComponentPeer* currentPeer = nullptr; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) +}; + +//============================================================================== +AndroidViewComponent::AndroidViewComponent() {} +AndroidViewComponent::~AndroidViewComponent() {} + +void AndroidViewComponent::setView (void* const view) +{ + if (view != getView()) + { + pimpl = nullptr; + + if (view != nullptr) + pimpl = new Pimpl ((jobject) view, *this); + } +} + +void* AndroidViewComponent::getView() const +{ + return pimpl == nullptr ? nullptr : (void*) pimpl->view; +} + +void AndroidViewComponent::resizeToFitView() +{ + if (pimpl != nullptr) + setBounds (pimpl->getViewBounds()); +} + +void AndroidViewComponent::paint (Graphics&) {} + +} // namespace juce diff --git a/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp b/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp index ca671a011f..a3fe0c1826 100644 --- a/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp +++ b/modules/juce_gui_extra/native/juce_android_PushNotifications.cpp @@ -73,12 +73,6 @@ DECLARE_JNI_CLASS (BitmapConfig, "android/graphics/Bitmap$Config"); DECLARE_JNI_CLASS (Bundle, "android/os/Bundle"); #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (toString, "toString", "()Ljava/lang/String;") - -DECLARE_JNI_CLASS (CharSequence, "java/lang/CharSequence"); -#undef JNI_CLASS_MEMBERS - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ METHOD (hasNext, "hasNext", "()Z") \ METHOD (next, "next", "()Ljava/lang/Object;") @@ -86,19 +80,6 @@ DECLARE_JNI_CLASS (CharSequence, "java/lang/CharSequence"); DECLARE_JNI_CLASS (Iterator, "java/util/Iterator"); #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (get, "get", "(Ljava/lang/Object;)Ljava/lang/Object;") \ - METHOD (keySet, "keySet", "()Ljava/util/Set;") - -DECLARE_JNI_CLASS (JavaMap, "java/util/Map"); -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (concat, "concat", "(Ljava/lang/String;)Ljava/lang/String;") - -DECLARE_JNI_CLASS (JavaString, "java/lang/String"); -#undef JNI_CLASS_MEMBERS - #if __ANDROID_API__ >= 26 #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ METHOD (constructor, "", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \ @@ -529,7 +510,7 @@ struct PushNotifications::Pimpl if (remoteInputResult.get() != 0) { auto charSequence = LocalRef (env->CallObjectMethod (remoteInputResult, Bundle.getCharSequence, resultKeyString.get())); - auto responseString = LocalRef ((jstring) env->CallObjectMethod (charSequence, CharSequence.toString)); + auto responseString = LocalRef ((jstring) env->CallObjectMethod (charSequence, JavaCharSequence.toString)); owner.listeners.call (&PushNotifications::Listener::handleNotificationAction, true, diff --git a/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp index 7fc51d7cc7..a3b2bca521 100644 --- a/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_android_WebBrowserComponent.cpp @@ -27,16 +27,360 @@ namespace juce { -WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) - : browser (nullptr), - blankPageShown (false), - unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) -{ - // Unfortunately, WebBrowserComponent is not implemented for Android yet! - // This is just a stub implementation without any useful functionality. - jassertfalse; +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "(Landroid/content/Context;)V") \ + METHOD (getSettings, "getSettings", "()Landroid/webkit/WebSettings;") \ + 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) \ + METHOD (constructor, "", "()V") + +DECLARE_JNI_CLASS (AndroidWebChromeClient, "android/webkit/WebChromeClient"); +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "()V") + +DECLARE_JNI_CLASS (AndroidWebViewClient, "android/webkit/WebViewClient"); +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + STATICMETHOD (getInstance, "getInstance", "()Landroid/webkit/CookieManager;") + +DECLARE_JNI_CLASS (AndroidCookieManager, "android/webkit/CookieManager"); +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") + +DECLARE_JNI_CLASS (JuceWebChromeClient, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceWebChromeClient"); +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + METHOD (constructor, "", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH ";J)V") \ + METHOD (hostDeleted, "hostDeleted", "()V") + +DECLARE_JNI_CLASS (JuceWebViewClient, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceWebViewClient"); +#undef JNI_CLASS_MEMBERS + +#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ + 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) \ + 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) \ + 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, android.activity.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, + android.activity.get(), + reinterpret_cast(&owner)))); + env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get()); + + juceWebViewClient = GlobalRef (LocalRef (env->NewObject (JuceWebViewClient, JuceWebViewClient.constructor, + android.activity.get(), + reinterpret_cast(&owner)))); + 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, JuceWebViewClient.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 = nullptr; + connectionThread = new ConnectionThread (*this, urlToUse, *headers); + } + } + + void stop() + { + connectionThread = nullptr; + + getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading); + } + + void goBack() + { + connectionThread = nullptr; + + getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.goBack); + } + + 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; + } + +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() + { + 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; + + while (true) + { + if (threadShouldExit()) + return; + + char buffer [8192]; + const int num = webInputStream->read (buffer, sizeof (buffer)); + + if (num <= 0) + break; + + ostream.write (buffer, (size_t) num); + } + + result.data = ostream.toUTF8(); + } + + Pimpl& owner; + ScopedPointer webInputStream; + Result result; + }; + + + WebBrowserComponent& owner; + GlobalRef juceWebChromeClient; + GlobalRef juceWebViewClient; + ScopedPointer connectionThread; + WaitableEvent responseReadyEvent; + + WeakReference::Master masterReference; + friend class WeakReference; +}; + +//============================================================================== +WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden) + : blankPageShown (false), + unloadPageWhenBrowserIsHidden (unloadWhenHidden) +{ setOpaque (true); + + addAndMakeVisible (browser = new Pimpl (*this)); } WebBrowserComponent::~WebBrowserComponent() @@ -61,10 +405,13 @@ void WebBrowserComponent::goToURL (const String& url, lastPostData.reset(); blankPageShown = false; + + browser->goToURL (url, headers, postData); } void WebBrowserComponent::stop() { + browser->stop(); } void WebBrowserComponent::goBack() @@ -72,16 +419,19 @@ void WebBrowserComponent::goBack() lastURL.clear(); blankPageShown = false; + browser->goBack(); } void WebBrowserComponent::goForward() { lastURL.clear(); + browser->goForward(); } void WebBrowserComponent::refresh() { + browser->refresh(); } //============================================================================== @@ -92,13 +442,30 @@ void WebBrowserComponent::paint (Graphics& g) void WebBrowserComponent::checkWindowAssociation() { + if (isShowing()) + { + if (blankPageShown) + goBack(); + } + else + { + if (unloadPageWhenBrowserIsHidden && ! 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", 0, 0); + } + } } void WebBrowserComponent::reloadLastURL() { if (lastURL.isNotEmpty()) { - goToURL (lastURL, &lastHeaders, &lastPostData); + goToURL (lastURL, &lastHeaders, lastPostData.getSize() == 0 ? nullptr : &lastPostData); lastURL.clear(); } } @@ -110,6 +477,7 @@ void WebBrowserComponent::parentHierarchyChanged() void WebBrowserComponent::resized() { + browser->setSize (getWidth(), getHeight()); } void WebBrowserComponent::visibilityChanged() @@ -123,6 +491,118 @@ void WebBrowserComponent::focusGained (FocusChangeType) void WebBrowserComponent::clearCookies() { + auto* env = getEnv(); + + auto cookieManager = LocalRef (env->CallStaticObjectMethod (AndroidCookieManager, + AndroidCookieManager.getInstance)); + + const bool apiAtLeast21 = env->CallStaticIntMethod (JuceAppActivity, JuceAppActivity.getAndroidSDKVersion) >= 21; + + jmethodID clearCookiesMethod = 0; + + if (apiAtLeast21) + { + 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); + } } +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewPageLoadStarted, bool, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject url)) +{ + setEnv (env); + + return juce_webViewPageLoadStarted (reinterpret_cast (host), + juceString (static_cast (url))); +} + +bool juce_webViewPageLoadStarted (WebBrowserComponent* browserComponent, const String& url) +{ + return browserComponent->browser->handlePageAboutToLoad (url); +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewPageLoadFinished, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject url)) +{ + setEnv (env); + + reinterpret_cast (host)->pageFinishedLoading (juceString (static_cast (url))); +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject error)) +{ + setEnv (env); + + jclass errorClass = env->FindClass ("android/webkit/WebResourceError"); + + if (errorClass != 0) + { + jmethodID method = env->GetMethodID (errorClass, "getDescription", "()Ljava/lang/CharSequence;"); + + if (method != 0) + { + auto sequence = LocalRef (env->CallObjectMethod (error, method)); + auto errorString = LocalRef ((jstring) env->CallObjectMethod (sequence, JavaCharSequence.toString)); + + reinterpret_cast (host)->pageLoadHadNetworkError (juceString (errorString)); + return; + } + } + + // Should never get here! + jassertfalse; + reinterpret_cast (host)->pageLoadHadNetworkError ({}); +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedHttpError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*request*/, jobject errorResponse)) +{ + setEnv (env); + + jclass responseClass = env->FindClass ("android/webkit/WebResourceResponse"); + + if (responseClass != 0) + { + jmethodID method = env->GetMethodID (responseClass, "getReasonPhrase", "()Ljava/lang/String;"); + + if (method != 0) + { + auto errorString = LocalRef ((jstring) env->CallObjectMethod (errorResponse, method)); + + reinterpret_cast (host)->pageLoadHadNetworkError (juceString (errorString)); + return; + } + } + + // Should never get here! + jassertfalse; + reinterpret_cast (host)->pageLoadHadNetworkError ({}); +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewReceivedSslError, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError)) +{ + setEnv (env); + + auto errorString = LocalRef ((jstring) env->CallObjectMethod (sslError, SslError.toString)); + + reinterpret_cast (host)->pageLoadHadNetworkError (juceString (errorString)); +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewCloseWindowRequest, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/)) +{ + setEnv (env); + + reinterpret_cast (host)->windowCloseRequest(); +} + +JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewCreateWindowRequest, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/)) +{ + setEnv (env); + + reinterpret_cast (host)->newWindowAttemptingToLoad ({}); +} + + } // namespace juce diff --git a/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp index 6a8f0da89d..56348bfca3 100644 --- a/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_linux_X11_WebBrowserComponent.cpp @@ -715,11 +715,6 @@ WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidd WebBrowserComponent::~WebBrowserComponent() { - if (browser != nullptr) - { - delete browser; - browser = nullptr; - } } //============================================================================== diff --git a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index 03078226ba..6ac198150b 100644 --- a/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -358,8 +358,7 @@ private: //============================================================================== WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden) - : browser (nullptr), - blankPageShown (false), + : blankPageShown (false), unloadPageWhenBrowserIsHidden (unloadWhenHidden) { setOpaque (true); @@ -369,7 +368,6 @@ WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden) WebBrowserComponent::~WebBrowserComponent() { - deleteAndZero (browser); } //============================================================================== diff --git a/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp b/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp index 4fa2c11622..935b2c907a 100644 --- a/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/native/juce_win32_WebBrowserComponent.cpp @@ -241,17 +241,15 @@ private: //============================================================================== WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) - : browser (nullptr), + : browser (new Pimpl()), blankPageShown (false), unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) { setOpaque (true); - addAndMakeVisible (browser = new Pimpl()); } WebBrowserComponent::~WebBrowserComponent() { - delete browser; } //============================================================================== diff --git a/modules/juce_opengl/native/juce_OpenGL_android.h b/modules/juce_opengl/native/juce_OpenGL_android.h index 60f9fdc43d..91b9c05e23 100644 --- a/modules/juce_opengl/native/juce_OpenGL_android.h +++ b/modules/juce_opengl/native/juce_OpenGL_android.h @@ -35,13 +35,6 @@ namespace juce DECLARE_JNI_CLASS (NativeSurfaceView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (addView, "addView", "(Landroid/view/View;)V") \ - METHOD (removeView, "removeView", "(Landroid/view/View;)V") \ - -DECLARE_JNI_CLASS (AndroidViewGroup, "android/view/ViewGroup") -#undef JNI_CLASS_MEMBERS - //============================================================================== class OpenGLContext::NativeContext {