1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Android: add WebBrowserComponent implementation.

This commit is contained in:
Lukasz Kozakiewicz 2017-11-05 22:54:20 +01:00
parent e2ae08e111
commit 073921445c
16 changed files with 1014 additions and 80 deletions

View file

@ -1007,6 +1007,51 @@ private:
.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");
auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString());
@ -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;

View file

@ -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$$

View file

@ -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();

View file

@ -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, "<init>", "()V") \
METHOD (constructorWithCapacity, "<init>", "(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;

View file

@ -188,21 +188,11 @@ 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 (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<int> getBounds() const override
{
return (Rectangle<float> (view.callIntMethod (ComponentPeerView.getLeft),
view.callIntMethod (ComponentPeerView.getTop),
view.callIntMethod (ComponentPeerView.getWidth),
view.callIntMethod (ComponentPeerView.getHeight)) / scale).toNearestInt();
return (Rectangle<float> (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<float> getScreenPosition() const
{
return Point<float> (view.callIntMethod (ComponentPeerView.getLeft),
view.callIntMethod (ComponentPeerView.getTop)) / scale;
return Point<float> (view.callIntMethod (AndroidView.getLeft),
view.callIntMethod (AndroidView.getTop)) / scale;
}
Point<float> localToGlobal (Point<float> 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());
}

View file

@ -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> pimpl;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidViewComponent)
};
#endif
} // namespace juce

View file

@ -163,6 +163,8 @@
//==============================================================================
#elif JUCE_ANDROID
#include "native/juce_AndroidViewComponent.cpp"
#if JUCE_WEB_BROWSER
#include "native/juce_android_WebBrowserComponent.cpp"
#endif

View file

@ -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"

View file

@ -136,7 +136,7 @@ public:
private:
//==============================================================================
class Pimpl;
Pimpl* browser;
ScopedPointer<Pimpl> 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)
};

View file

@ -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<int>());
Rectangle<int> 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<int> getViewBounds() const
{
auto* env = getEnv();
int width = env->CallIntMethod (view, AndroidView.getWidth);
int height = env->CallIntMethod (view, AndroidView.getHeight);
return Rectangle<int> (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

View file

@ -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, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V") \
@ -529,7 +510,7 @@ struct PushNotifications::Pimpl
if (remoteInputResult.get() != 0)
{
auto charSequence = LocalRef<jobject> (env->CallObjectMethod (remoteInputResult, Bundle.getCharSequence, resultKeyString.get()));
auto responseString = LocalRef<jstring> ((jstring) env->CallObjectMethod (charSequence, CharSequence.toString));
auto responseString = LocalRef<jstring> ((jstring) env->CallObjectMethod (charSequence, JavaCharSequence.toString));
owner.listeners.call (&PushNotifications::Listener::handleNotificationAction,
true,

View file

@ -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, "<init>", "(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, "<init>", "()V")
DECLARE_JNI_CLASS (AndroidWebChromeClient, "android/webkit/WebChromeClient");
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
METHOD (constructor, "<init>", "()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, "<init>", "(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, "<init>", "(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<jobject> (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<jobject> (env->NewObject (JuceWebChromeClient, JuceWebChromeClient.constructor,
android.activity.get(),
reinterpret_cast<jlong>(&owner))));
env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get());
juceWebViewClient = GlobalRef (LocalRef<jobject> (env->NewObject (JuceWebViewClient, JuceWebViewClient.constructor,
android.activity.get(),
reinterpret_cast<jlong>(&owner))));
env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebViewClient, juceWebViewClient.get());
}
~Pimpl()
{
auto* env = getEnv();
env->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading);
auto defaultChromeClient = LocalRef<jobject> (env->NewObject (AndroidWebChromeClient, AndroidWebChromeClient.constructor));
auto defaultViewClient = LocalRef<jobject> (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<jobject> (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<jobject> (env->CallStaticObjectMethod (URLEncoder, URLEncoder.encode,
dataStringJava.get(), javaString ("utf-8").get()));
auto bytes = LocalRef<jbyteArray> ((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<Pimpl> 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> webInputStream;
Result result;
};
WebBrowserComponent& owner;
GlobalRef juceWebChromeClient;
GlobalRef juceWebViewClient;
ScopedPointer<ConnectionThread> connectionThread;
WaitableEvent responseReadyEvent;
WeakReference<Pimpl>::Master masterReference;
friend class WeakReference<Pimpl>;
};
//==============================================================================
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<jobject> (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<WebBrowserComponent*> (host),
juceString (static_cast<jstring> (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<WebBrowserComponent*> (host)->pageFinishedLoading (juceString (static_cast<jstring> (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<jobject> (env->CallObjectMethod (error, method));
auto errorString = LocalRef<jstring> ((jstring) env->CallObjectMethod (sequence, JavaCharSequence.toString));
reinterpret_cast<WebBrowserComponent*> (host)->pageLoadHadNetworkError (juceString (errorString));
return;
}
}
// Should never get here!
jassertfalse;
reinterpret_cast<WebBrowserComponent*> (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> ((jstring) env->CallObjectMethod (errorResponse, method));
reinterpret_cast<WebBrowserComponent*> (host)->pageLoadHadNetworkError (juceString (errorString));
return;
}
}
// Should never get here!
jassertfalse;
reinterpret_cast<WebBrowserComponent*> (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> ((jstring) env->CallObjectMethod (sslError, SslError.toString));
reinterpret_cast<WebBrowserComponent*> (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<WebBrowserComponent*> (host)->windowCloseRequest();
}
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, webViewCreateWindowRequest, void, (JNIEnv* env, jobject /*activity*/, jlong host, jobject /*webView*/))
{
setEnv (env);
reinterpret_cast<WebBrowserComponent*> (host)->newWindowAttemptingToLoad ({});
}
} // namespace juce

View file

@ -715,11 +715,6 @@ WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidd
WebBrowserComponent::~WebBrowserComponent()
{
if (browser != nullptr)
{
delete browser;
browser = nullptr;
}
}
//==============================================================================

View file

@ -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);
}
//==============================================================================

View file

@ -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;
}
//==============================================================================

View file

@ -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
{