diff options
Diffstat (limited to 'widget/android/AndroidBridge.cpp')
-rw-r--r-- | widget/android/AndroidBridge.cpp | 1126 |
1 files changed, 1126 insertions, 0 deletions
diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp new file mode 100644 index 000000000..dd2cce39a --- /dev/null +++ b/widget/android/AndroidBridge.cpp @@ -0,0 +1,1126 @@ +/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <android/log.h> +#include <dlfcn.h> +#include <math.h> +#include <GLES2/gl2.h> + +#include "mozilla/layers/CompositorBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" + +#include "mozilla/Hal.h" +#include "nsXULAppAPI.h" +#include <prthread.h> +#include "nsXPCOMStrings.h" +#include "AndroidBridge.h" +#include "AndroidJNIWrapper.h" +#include "AndroidBridgeUtilities.h" +#include "nsAlertsUtils.h" +#include "nsAppShell.h" +#include "nsOSHelperAppService.h" +#include "nsWindow.h" +#include "mozilla/Preferences.h" +#include "nsThreadUtils.h" +#include "nsIThreadManager.h" +#include "gfxPlatform.h" +#include "gfxContext.h" +#include "mozilla/gfx/2D.h" +#include "gfxUtils.h" +#include "nsPresContext.h" +#include "nsIDocShell.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "nsIDOMWindowUtils.h" +#include "nsIDOMClientRect.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsPrintfCString.h" +#include "NativeJSContainer.h" +#include "nsContentUtils.h" +#include "nsIScriptError.h" +#include "nsIHttpChannel.h" + +#include "MediaCodec.h" +#include "SurfaceTexture.h" +#include "GLContextProvider.h" + +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ContentChild.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "MediaPrefs.h" +#include "WidgetUtils.h" + +#include "FennecJNIWrappers.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::jni; +using namespace mozilla::java; + +AndroidBridge* AndroidBridge::sBridge = nullptr; +static jobject sGlobalContext = nullptr; +nsDataHashtable<nsStringHashKey, nsString> AndroidBridge::sStoragePaths; + +jmethodID AndroidBridge::GetMethodID(JNIEnv* env, jclass jClass, + const char* methodName, const char* methodType) +{ + jmethodID methodID = env->GetMethodID(jClass, methodName, methodType); + if (!methodID) { + ALOG(">>> FATAL JNI ERROR! GetMethodID(methodName=\"%s\", " + "methodType=\"%s\") failed. Did ProGuard optimize away something it shouldn't have?", + methodName, methodType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return methodID; +} + +jmethodID AndroidBridge::GetStaticMethodID(JNIEnv* env, jclass jClass, + const char* methodName, const char* methodType) +{ + jmethodID methodID = env->GetStaticMethodID(jClass, methodName, methodType); + if (!methodID) { + ALOG(">>> FATAL JNI ERROR! GetStaticMethodID(methodName=\"%s\", " + "methodType=\"%s\") failed. Did ProGuard optimize away something it shouldn't have?", + methodName, methodType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return methodID; +} + +jfieldID AndroidBridge::GetFieldID(JNIEnv* env, jclass jClass, + const char* fieldName, const char* fieldType) +{ + jfieldID fieldID = env->GetFieldID(jClass, fieldName, fieldType); + if (!fieldID) { + ALOG(">>> FATAL JNI ERROR! GetFieldID(fieldName=\"%s\", " + "fieldType=\"%s\") failed. Did ProGuard optimize away something it shouldn't have?", + fieldName, fieldType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return fieldID; +} + +jfieldID AndroidBridge::GetStaticFieldID(JNIEnv* env, jclass jClass, + const char* fieldName, const char* fieldType) +{ + jfieldID fieldID = env->GetStaticFieldID(jClass, fieldName, fieldType); + if (!fieldID) { + ALOG(">>> FATAL JNI ERROR! GetStaticFieldID(fieldName=\"%s\", " + "fieldType=\"%s\") failed. Did ProGuard optimize away something it shouldn't have?", + fieldName, fieldType); + env->ExceptionDescribe(); + MOZ_CRASH(); + } + return fieldID; +} + +void +AndroidBridge::ConstructBridge() +{ + /* NSS hack -- bionic doesn't handle recursive unloads correctly, + * because library finalizer functions are called with the dynamic + * linker lock still held. This results in a deadlock when trying + * to call dlclose() while we're already inside dlclose(). + * Conveniently, NSS has an env var that can prevent it from unloading. + */ + putenv("NSS_DISABLE_UNLOAD=1"); + + MOZ_ASSERT(!sBridge); + sBridge = new AndroidBridge(); + + MediaPrefs::GetSingleton(); +} + +void +AndroidBridge::DeconstructBridge() +{ + if (sBridge) { + delete sBridge; + // AndroidBridge destruction requires sBridge to still be valid, + // so we set sBridge to nullptr after deleting it. + sBridge = nullptr; + } +} + +AndroidBridge::~AndroidBridge() +{ +} + +AndroidBridge::AndroidBridge() + : mUiTaskQueueLock("UiTaskQueue") +{ + ALOG_BRIDGE("AndroidBridge::Init"); + + JNIEnv* const jEnv = jni::GetGeckoThreadEnv(); + AutoLocalJNIFrame jniFrame(jEnv); + + mMessageQueue = java::GeckoThread::MsgQueue(); + auto msgQueueClass = Class::LocalRef::Adopt( + jEnv, jEnv->GetObjectClass(mMessageQueue.Get())); + // mMessageQueueNext must not be null + mMessageQueueNext = GetMethodID( + jEnv, msgQueueClass.Get(), "next", "()Landroid/os/Message;"); + // mMessageQueueMessages may be null (e.g. due to proguard optimization) + mMessageQueueMessages = jEnv->GetFieldID( + msgQueueClass.Get(), "mMessages", "Landroid/os/Message;"); + + AutoJNIClass string(jEnv, "java/lang/String"); + jStringClass = string.getGlobalRef(); + + if (!GetStaticIntField("android/os/Build$VERSION", "SDK_INT", &mAPIVersion, jEnv)) { + ALOG_BRIDGE("Failed to find API version"); + } + + AutoJNIClass channels(jEnv, "java/nio/channels/Channels"); + jChannels = channels.getGlobalRef(); + jChannelCreate = channels.getStaticMethod("newChannel", "(Ljava/io/InputStream;)Ljava/nio/channels/ReadableByteChannel;"); + + AutoJNIClass readableByteChannel(jEnv, "java/nio/channels/ReadableByteChannel"); + jReadableByteChannel = readableByteChannel.getGlobalRef(); + jByteBufferRead = readableByteChannel.getMethod("read", "(Ljava/nio/ByteBuffer;)I"); + + AutoJNIClass inputStream(jEnv, "java/io/InputStream"); + jInputStream = inputStream.getGlobalRef(); + jClose = inputStream.getMethod("close", "()V"); + jAvailable = inputStream.getMethod("available", "()I"); +} + +// Raw JNIEnv variants. +jstring AndroidBridge::NewJavaString(JNIEnv* env, const char16_t* string, uint32_t len) { + jstring ret = env->NewString(reinterpret_cast<const jchar*>(string), len); + if (env->ExceptionCheck()) { + ALOG_BRIDGE("Exceptional exit of: %s", __PRETTY_FUNCTION__); + env->ExceptionDescribe(); + env->ExceptionClear(); + return nullptr; + } + return ret; +} + +jstring AndroidBridge::NewJavaString(JNIEnv* env, const nsAString& string) { + return NewJavaString(env, string.BeginReading(), string.Length()); +} + +jstring AndroidBridge::NewJavaString(JNIEnv* env, const char* string) { + return NewJavaString(env, NS_ConvertUTF8toUTF16(string)); +} + +jstring AndroidBridge::NewJavaString(JNIEnv* env, const nsACString& string) { + return NewJavaString(env, NS_ConvertUTF8toUTF16(string)); +} + +// AutoLocalJNIFrame variants.. +jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const char16_t* string, uint32_t len) { + return NewJavaString(frame->GetEnv(), string, len); +} + +jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const nsAString& string) { + return NewJavaString(frame, string.BeginReading(), string.Length()); +} + +jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const char* string) { + return NewJavaString(frame, NS_ConvertUTF8toUTF16(string)); +} + +jstring AndroidBridge::NewJavaString(AutoLocalJNIFrame* frame, const nsACString& string) { + return NewJavaString(frame, NS_ConvertUTF8toUTF16(string)); +} + +static void +getHandlersFromStringArray(JNIEnv *aJNIEnv, jobjectArray jArr, jsize aLen, + nsIMutableArray *aHandlersArray, + nsIHandlerApp **aDefaultApp, + const nsAString& aAction = EmptyString(), + const nsACString& aMimeType = EmptyCString()) +{ + nsString empty = EmptyString(); + for (jsize i = 0; i < aLen; i+=4) { + + AutoLocalJNIFrame jniFrame(aJNIEnv, 4); + nsJNIString name( + static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i)), aJNIEnv); + nsJNIString isDefault( + static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i + 1)), aJNIEnv); + nsJNIString packageName( + static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i + 2)), aJNIEnv); + nsJNIString className( + static_cast<jstring>(aJNIEnv->GetObjectArrayElement(jArr, i + 3)), aJNIEnv); + nsIHandlerApp* app = nsOSHelperAppService:: + CreateAndroidHandlerApp(name, className, packageName, + className, aMimeType, aAction); + + aHandlersArray->AppendElement(app, false); + if (aDefaultApp && isDefault.Length() > 0) + *aDefaultApp = app; + } +} + +bool +AndroidBridge::GetHandlersForMimeType(const nsAString& aMimeType, + nsIMutableArray *aHandlersArray, + nsIHandlerApp **aDefaultApp, + const nsAString& aAction) +{ + ALOG_BRIDGE("AndroidBridge::GetHandlersForMimeType"); + + auto arr = GeckoAppShell::GetHandlersForMimeType(aMimeType, aAction); + if (!arr) + return false; + + JNIEnv* const env = arr.Env(); + jsize len = env->GetArrayLength(arr.Get()); + + if (!aHandlersArray) + return len > 0; + + getHandlersFromStringArray(env, arr.Get(), len, aHandlersArray, + aDefaultApp, aAction, + NS_ConvertUTF16toUTF8(aMimeType)); + return true; +} + +bool +AndroidBridge::GetHWEncoderCapability() +{ + ALOG_BRIDGE("AndroidBridge::GetHWEncoderCapability"); + + bool value = GeckoAppShell::GetHWEncoderCapability(); + + return value; +} + + +bool +AndroidBridge::GetHWDecoderCapability() +{ + ALOG_BRIDGE("AndroidBridge::GetHWDecoderCapability"); + + bool value = GeckoAppShell::GetHWDecoderCapability(); + + return value; +} + +bool +AndroidBridge::GetHandlersForURL(const nsAString& aURL, + nsIMutableArray* aHandlersArray, + nsIHandlerApp **aDefaultApp, + const nsAString& aAction) +{ + ALOG_BRIDGE("AndroidBridge::GetHandlersForURL"); + + auto arr = GeckoAppShell::GetHandlersForURL(aURL, aAction); + if (!arr) + return false; + + JNIEnv* const env = arr.Env(); + jsize len = env->GetArrayLength(arr.Get()); + + if (!aHandlersArray) + return len > 0; + + getHandlersFromStringArray(env, arr.Get(), len, aHandlersArray, + aDefaultApp, aAction); + return true; +} + +void +AndroidBridge::GetMimeTypeFromExtensions(const nsACString& aFileExt, nsCString& aMimeType) +{ + ALOG_BRIDGE("AndroidBridge::GetMimeTypeFromExtensions"); + + auto jstrType = GeckoAppShell::GetMimeTypeFromExtensions(aFileExt); + + if (jstrType) { + aMimeType = jstrType->ToCString(); + } +} + +void +AndroidBridge::GetExtensionFromMimeType(const nsACString& aMimeType, nsACString& aFileExt) +{ + ALOG_BRIDGE("AndroidBridge::GetExtensionFromMimeType"); + + auto jstrExt = GeckoAppShell::GetExtensionFromMimeType(aMimeType); + + if (jstrExt) { + aFileExt = jstrExt->ToCString(); + } +} + +bool +AndroidBridge::GetClipboardText(nsAString& aText) +{ + ALOG_BRIDGE("AndroidBridge::GetClipboardText"); + + auto text = Clipboard::GetText(); + + if (text) { + aText = text->ToString(); + } + return !!text; +} + +int +AndroidBridge::GetDPI() +{ + static int sDPI = 0; + if (sDPI) + return sDPI; + + const int DEFAULT_DPI = 160; + + sDPI = GeckoAppShell::GetDpi(); + if (!sDPI) { + return DEFAULT_DPI; + } + + return sDPI; +} + +int +AndroidBridge::GetScreenDepth() +{ + ALOG_BRIDGE("%s", __PRETTY_FUNCTION__); + + static int sDepth = 0; + if (sDepth) + return sDepth; + + const int DEFAULT_DEPTH = 16; + + if (jni::IsAvailable()) { + sDepth = GeckoAppShell::GetScreenDepth(); + } + if (!sDepth) + return DEFAULT_DEPTH; + + return sDepth; +} +void +AndroidBridge::Vibrate(const nsTArray<uint32_t>& aPattern) +{ + ALOG_BRIDGE("%s", __PRETTY_FUNCTION__); + + uint32_t len = aPattern.Length(); + if (!len) { + ALOG_BRIDGE(" invalid 0-length array"); + return; + } + + // It's clear if this worth special-casing, but it creates less + // java junk, so dodges the GC. + if (len == 1) { + jlong d = aPattern[0]; + if (d < 0) { + ALOG_BRIDGE(" invalid vibration duration < 0"); + return; + } + GeckoAppShell::Vibrate(d); + return; + } + + // First element of the array vibrate() expects is how long to wait + // *before* vibrating. For us, this is always 0. + + JNIEnv* const env = jni::GetGeckoThreadEnv(); + AutoLocalJNIFrame jniFrame(env, 1); + + jlongArray array = env->NewLongArray(len + 1); + if (!array) { + ALOG_BRIDGE(" failed to allocate array"); + return; + } + + jlong* elts = env->GetLongArrayElements(array, nullptr); + elts[0] = 0; + for (uint32_t i = 0; i < aPattern.Length(); ++i) { + jlong d = aPattern[i]; + if (d < 0) { + ALOG_BRIDGE(" invalid vibration duration < 0"); + env->ReleaseLongArrayElements(array, elts, JNI_ABORT); + return; + } + elts[i + 1] = d; + } + env->ReleaseLongArrayElements(array, elts, 0); + + GeckoAppShell::Vibrate(LongArray::Ref::From(array), -1 /* don't repeat */); +} + +void +AndroidBridge::GetSystemColors(AndroidSystemColors *aColors) +{ + + NS_ASSERTION(aColors != nullptr, "AndroidBridge::GetSystemColors: aColors is null!"); + if (!aColors) + return; + + auto arr = GeckoAppShell::GetSystemColors(); + if (!arr) + return; + + JNIEnv* const env = arr.Env(); + uint32_t len = static_cast<uint32_t>(env->GetArrayLength(arr.Get())); + jint *elements = env->GetIntArrayElements(arr.Get(), 0); + + uint32_t colorsCount = sizeof(AndroidSystemColors) / sizeof(nscolor); + if (len < colorsCount) + colorsCount = len; + + // Convert Android colors to nscolor by switching R and B in the ARGB 32 bit value + nscolor *colors = (nscolor*)aColors; + + for (uint32_t i = 0; i < colorsCount; i++) { + uint32_t androidColor = static_cast<uint32_t>(elements[i]); + uint8_t r = (androidColor & 0x00ff0000) >> 16; + uint8_t b = (androidColor & 0x000000ff); + colors[i] = (androidColor & 0xff00ff00) | (b << 16) | r; + } + + env->ReleaseIntArrayElements(arr.Get(), elements, 0); +} + +void +AndroidBridge::GetIconForExtension(const nsACString& aFileExt, uint32_t aIconSize, uint8_t * const aBuf) +{ + ALOG_BRIDGE("AndroidBridge::GetIconForExtension"); + NS_ASSERTION(aBuf != nullptr, "AndroidBridge::GetIconForExtension: aBuf is null!"); + if (!aBuf) + return; + + auto arr = GeckoAppShell::GetIconForExtension(NS_ConvertUTF8toUTF16(aFileExt), aIconSize); + + NS_ASSERTION(arr != nullptr, "AndroidBridge::GetIconForExtension: Returned pixels array is null!"); + if (!arr) + return; + + JNIEnv* const env = arr.Env(); + uint32_t len = static_cast<uint32_t>(env->GetArrayLength(arr.Get())); + jbyte *elements = env->GetByteArrayElements(arr.Get(), 0); + + uint32_t bufSize = aIconSize * aIconSize * 4; + NS_ASSERTION(len == bufSize, "AndroidBridge::GetIconForExtension: Pixels array is incomplete!"); + if (len == bufSize) + memcpy(aBuf, elements, bufSize); + + env->ReleaseByteArrayElements(arr.Get(), elements, 0); +} + +bool +AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, int32_t* aInt, JNIEnv* jEnv /* = nullptr */) +{ + ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName); + + if (!jEnv) { + if (!jni::IsAvailable()) { + return false; + } + jEnv = jni::GetGeckoThreadEnv(); + } + + AutoJNIClass cls(jEnv, className); + jfieldID field = cls.getStaticField(fieldName, "I"); + + if (!field) { + return false; + } + + *aInt = static_cast<int32_t>(jEnv->GetStaticIntField(cls.getRawRef(), field)); + return true; +} + +bool +AndroidBridge::GetStaticStringField(const char *className, const char *fieldName, nsAString &result, JNIEnv* jEnv /* = nullptr */) +{ + ALOG_BRIDGE("AndroidBridge::GetStaticStringField %s", fieldName); + + if (!jEnv) { + if (!jni::IsAvailable()) { + return false; + } + jEnv = jni::GetGeckoThreadEnv(); + } + + AutoLocalJNIFrame jniFrame(jEnv, 1); + AutoJNIClass cls(jEnv, className); + jfieldID field = cls.getStaticField(fieldName, "Ljava/lang/String;"); + + if (!field) { + return false; + } + + jstring jstr = (jstring) jEnv->GetStaticObjectField(cls.getRawRef(), field); + if (!jstr) + return false; + + result.Assign(nsJNIString(jstr, jEnv)); + return true; +} + +namespace mozilla { + class TracerRunnable : public Runnable{ + public: + TracerRunnable() { + mTracerLock = new Mutex("TracerRunnable"); + mTracerCondVar = new CondVar(*mTracerLock, "TracerRunnable"); + mMainThread = do_GetMainThread(); + + } + ~TracerRunnable() { + delete mTracerCondVar; + delete mTracerLock; + mTracerLock = nullptr; + mTracerCondVar = nullptr; + } + + virtual nsresult Run() { + MutexAutoLock lock(*mTracerLock); + if (!AndroidBridge::Bridge()) + return NS_OK; + + mHasRun = true; + mTracerCondVar->Notify(); + return NS_OK; + } + + bool Fire() { + if (!mTracerLock || !mTracerCondVar) + return false; + MutexAutoLock lock(*mTracerLock); + mHasRun = false; + mMainThread->Dispatch(this, NS_DISPATCH_NORMAL); + while (!mHasRun) + mTracerCondVar->Wait(); + return true; + } + + void Signal() { + MutexAutoLock lock(*mTracerLock); + mHasRun = true; + mTracerCondVar->Notify(); + } + private: + Mutex* mTracerLock; + CondVar* mTracerCondVar; + bool mHasRun; + nsCOMPtr<nsIThread> mMainThread; + + }; + StaticRefPtr<TracerRunnable> sTracerRunnable; + + bool InitWidgetTracing() { + if (!sTracerRunnable) + sTracerRunnable = new TracerRunnable(); + return true; + } + + void CleanUpWidgetTracing() { + sTracerRunnable = nullptr; + } + + bool FireAndWaitForTracerEvent() { + if (sTracerRunnable) + return sTracerRunnable->Fire(); + return false; + } + + void SignalTracerThread() + { + if (sTracerRunnable) + return sTracerRunnable->Signal(); + } + +} + + +void +AndroidBridge::GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) +{ + ALOG_BRIDGE("AndroidBridge::GetCurrentBatteryInformation"); + + // To prevent calling too many methods through JNI, the Java method returns + // an array of double even if we actually want a double and a boolean. + auto arr = GeckoAppShell::GetCurrentBatteryInformation(); + + JNIEnv* const env = arr.Env(); + if (!arr || env->GetArrayLength(arr.Get()) != 3) { + return; + } + + jdouble* info = env->GetDoubleArrayElements(arr.Get(), 0); + + aBatteryInfo->level() = info[0]; + aBatteryInfo->charging() = info[1] == 1.0f; + aBatteryInfo->remainingTime() = info[2]; + + env->ReleaseDoubleArrayElements(arr.Get(), info, 0); +} + +void +AndroidBridge::HandleGeckoMessage(JSContext* cx, JS::HandleObject object) +{ + ALOG_BRIDGE("%s", __PRETTY_FUNCTION__); + + auto message = widget::CreateNativeJSContainer(cx, object); + GeckoAppShell::HandleGeckoMessage(message); +} + +void +AndroidBridge::GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo) +{ + ALOG_BRIDGE("AndroidBridge::GetCurrentNetworkInformation"); + + // To prevent calling too many methods through JNI, the Java method returns + // an array of double even if we actually want an integer, a boolean, and an integer. + + auto arr = GeckoAppShell::GetCurrentNetworkInformation(); + + JNIEnv* const env = arr.Env(); + if (!arr || env->GetArrayLength(arr.Get()) != 3) { + return; + } + + jdouble* info = env->GetDoubleArrayElements(arr.Get(), 0); + + aNetworkInfo->type() = info[0]; + aNetworkInfo->isWifi() = info[1] == 1.0f; + aNetworkInfo->dhcpGateway() = info[2]; + + env->ReleaseDoubleArrayElements(arr.Get(), info, 0); +} + +jobject +AndroidBridge::GetGlobalContextRef() { + if (sGlobalContext) { + return sGlobalContext; + } + + JNIEnv* const env = GetEnvForThread(); + AutoLocalJNIFrame jniFrame(env, 4); + + auto context = GeckoAppShell::GetContext(); + if (!context) { + ALOG_BRIDGE("%s: Could not GetContext()", __FUNCTION__); + return 0; + } + jclass contextClass = env->FindClass("android/content/Context"); + if (!contextClass) { + ALOG_BRIDGE("%s: Could not find Context class.", __FUNCTION__); + return 0; + } + jmethodID mid = env->GetMethodID(contextClass, "getApplicationContext", + "()Landroid/content/Context;"); + if (!mid) { + ALOG_BRIDGE("%s: Could not find getApplicationContext.", __FUNCTION__); + return 0; + } + jobject appContext = env->CallObjectMethod(context.Get(), mid); + if (!appContext) { + ALOG_BRIDGE("%s: getApplicationContext failed.", __FUNCTION__); + return 0; + } + + sGlobalContext = env->NewGlobalRef(appContext); + MOZ_ASSERT(sGlobalContext); + return sGlobalContext; +} + +/* Implementation file */ +NS_IMPL_ISUPPORTS(nsAndroidBridge, nsIAndroidBridge) + +nsAndroidBridge::nsAndroidBridge() +{ + AddObservers(); +} + +nsAndroidBridge::~nsAndroidBridge() +{ + RemoveObservers(); +} + +NS_IMETHODIMP nsAndroidBridge::HandleGeckoMessage(JS::HandleValue val, + JSContext *cx) +{ + if (val.isObject()) { + JS::RootedObject object(cx, &val.toObject()); + AndroidBridge::Bridge()->HandleGeckoMessage(cx, object); + return NS_OK; + } + + // Now handle legacy JSON messages. + if (!val.isString()) { + return NS_ERROR_INVALID_ARG; + } + JS::RootedString jsonStr(cx, val.toString()); + + JS::RootedValue jsonVal(cx); + if (!JS_ParseJSON(cx, jsonStr, &jsonVal) || !jsonVal.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + // Spit out a warning before sending the message. + nsContentUtils::ReportToConsoleNonLocalized( + NS_LITERAL_STRING("Use of JSON is deprecated. " + "Please pass Javascript objects directly to handleGeckoMessage."), + nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("nsIAndroidBridge"), + nullptr); + + JS::RootedObject object(cx, &jsonVal.toObject()); + AndroidBridge::Bridge()->HandleGeckoMessage(cx, object); + return NS_OK; +} + +NS_IMETHODIMP nsAndroidBridge::ContentDocumentChanged(mozIDOMWindowProxy* aWindow) +{ + AndroidBridge::Bridge()->ContentDocumentChanged(aWindow); + return NS_OK; +} + +NS_IMETHODIMP nsAndroidBridge::IsContentDocumentDisplayed(mozIDOMWindowProxy* aWindow, + bool *aRet) +{ + *aRet = AndroidBridge::Bridge()->IsContentDocumentDisplayed(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsAndroidBridge::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, "xpcom-shutdown")) { + RemoveObservers(); + } else if (!strcmp(aTopic, "media-playback")) { + ALOG_BRIDGE("nsAndroidBridge::Observe, get media-playback event."); + + nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject); + if (!wrapper) { + return NS_OK; + } + + uint64_t windowId = 0; + nsresult rv = wrapper->GetData(&windowId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString activeStr(aData); + bool isPlaying = activeStr.EqualsLiteral("active"); + UpdateAudioPlayingWindows(windowId, isPlaying); + } + return NS_OK; +} + +void +nsAndroidBridge::UpdateAudioPlayingWindows(uint64_t aWindowId, + bool aPlaying) +{ + // Request audio focus for the first audio playing window and abandon focus + // for the last audio playing window. + if (aPlaying && !mAudioPlayingWindows.Contains(aWindowId)) { + mAudioPlayingWindows.AppendElement(aWindowId); + if (mAudioPlayingWindows.Length() == 1) { + ALOG_BRIDGE("nsAndroidBridge, request audio focus."); + AudioFocusAgent::NotifyStartedPlaying(); + } + } else if (!aPlaying && mAudioPlayingWindows.Contains(aWindowId)) { + mAudioPlayingWindows.RemoveElement(aWindowId); + if (mAudioPlayingWindows.Length() == 0) { + ALOG_BRIDGE("nsAndroidBridge, abandon audio focus."); + AudioFocusAgent::NotifyStoppedPlaying(); + } + } +} + +void +nsAndroidBridge::AddObservers() +{ + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "xpcom-shutdown", false); + if (jni::IsFennec()) { // No AudioFocusAgent in non-Fennec environment. + obs->AddObserver(this, "media-playback", false); + } + } +} + +void +nsAndroidBridge::RemoveObservers() +{ + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "xpcom-shutdown"); + if (jni::IsFennec()) { // No AudioFocusAgent in non-Fennec environment. + obs->RemoveObserver(this, "media-playback"); + } + } +} + +uint32_t +AndroidBridge::GetScreenOrientation() +{ + ALOG_BRIDGE("AndroidBridge::GetScreenOrientation"); + + int16_t orientation = GeckoAppShell::GetScreenOrientation(); + + if (!orientation) + return dom::eScreenOrientation_None; + + return static_cast<dom::ScreenOrientationInternal>(orientation); +} + +uint16_t +AndroidBridge::GetScreenAngle() +{ + return GeckoAppShell::GetScreenAngle(); +} + +nsresult +AndroidBridge::GetProxyForURI(const nsACString & aSpec, + const nsACString & aScheme, + const nsACString & aHost, + const int32_t aPort, + nsACString & aResult) +{ + if (!jni::IsAvailable()) { + return NS_ERROR_FAILURE; + } + + auto jstrRet = GeckoAppShell::GetProxyForURI(aSpec, aScheme, aHost, aPort); + + if (!jstrRet) + return NS_ERROR_FAILURE; + + aResult = jstrRet->ToCString(); + return NS_OK; +} + +bool +AndroidBridge::PumpMessageLoop() +{ + JNIEnv* const env = jni::GetGeckoThreadEnv(); + + if (mMessageQueueMessages) { + auto msg = Object::LocalRef::Adopt(env, + env->GetObjectField(mMessageQueue.Get(), + mMessageQueueMessages)); + // if queue.mMessages is null, queue.next() will block, which we don't + // want. It turns out to be an order of magnitude more performant to do + // this extra check here and block less vs. one fewer checks here and + // more blocking. + if (!msg) { + return false; + } + } + + auto msg = Object::LocalRef::Adopt( + env, env->CallObjectMethod(mMessageQueue.Get(), mMessageQueueNext)); + if (!msg) { + return false; + } + + return GeckoThread::PumpMessageLoop(msg); +} + +NS_IMETHODIMP nsAndroidBridge::GetBrowserApp(nsIAndroidBrowserApp * *aBrowserApp) +{ + nsAppShell* const appShell = nsAppShell::Get(); + if (appShell) + NS_IF_ADDREF(*aBrowserApp = appShell->GetBrowserApp()); + return NS_OK; +} + +NS_IMETHODIMP nsAndroidBridge::SetBrowserApp(nsIAndroidBrowserApp *aBrowserApp) +{ + nsAppShell* const appShell = nsAppShell::Get(); + if (appShell) + appShell->SetBrowserApp(aBrowserApp); + return NS_OK; +} + +extern "C" +__attribute__ ((visibility("default"))) +jobject JNICALL +Java_org_mozilla_gecko_GeckoAppShell_allocateDirectBuffer(JNIEnv *env, jclass, jlong size); + +static jni::DependentRef<java::GeckoLayerClient> +GetJavaLayerClient(mozIDOMWindowProxy* aWindow) +{ + MOZ_ASSERT(aWindow); + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = nsPIDOMWindowOuter::From(aWindow); + nsCOMPtr<nsIWidget> widget = + widget::WidgetUtils::DOMWindowToWidget(domWindow); + MOZ_ASSERT(widget); + + return static_cast<nsWindow*>(widget.get())->GetLayerClient(); +} + +void +AndroidBridge::ContentDocumentChanged(mozIDOMWindowProxy* aWindow) +{ + auto layerClient = GetJavaLayerClient(aWindow); + if (!layerClient) { + return; + } + layerClient->ContentDocumentChanged(); +} + +bool +AndroidBridge::IsContentDocumentDisplayed(mozIDOMWindowProxy* aWindow) +{ + auto layerClient = GetJavaLayerClient(aWindow); + if (!layerClient) { + return false; + } + return layerClient->IsContentDocumentDisplayed(); +} + +class AndroidBridge::DelayedTask +{ + using TimeStamp = mozilla::TimeStamp; + using TimeDuration = mozilla::TimeDuration; + +public: + DelayedTask(already_AddRefed<Runnable> aTask) + : mTask(aTask) + , mRunTime() // Null timestamp representing no delay. + {} + + DelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) + : mTask(aTask) + , mRunTime(TimeStamp::Now() + TimeDuration::FromMilliseconds(aDelayMs)) + {} + + bool IsEarlierThan(const DelayedTask& aOther) const + { + if (mRunTime) { + return aOther.mRunTime ? mRunTime < aOther.mRunTime : false; + } + // In the case of no delay, we're earlier if aOther has a delay. + // Otherwise, we're not earlier, to maintain task order. + return !!aOther.mRunTime; + } + + int64_t MillisecondsToRunTime() const + { + if (mRunTime) { + return int64_t((mRunTime - TimeStamp::Now()).ToMilliseconds()); + } + return 0; + } + + already_AddRefed<Runnable> TakeTask() + { + return mTask.forget(); + } + +private: + RefPtr<Runnable> mTask; + const TimeStamp mRunTime; +}; + + +void +AndroidBridge::PostTaskToUiThread(already_AddRefed<Runnable> aTask, int aDelayMs) +{ + // add the new task into the mUiTaskQueue, sorted with + // the earliest task first in the queue + size_t i; + DelayedTask newTask(aDelayMs ? DelayedTask(mozilla::Move(aTask), aDelayMs) + : DelayedTask(mozilla::Move(aTask))); + + { + MutexAutoLock lock(mUiTaskQueueLock); + + for (i = 0; i < mUiTaskQueue.Length(); i++) { + if (newTask.IsEarlierThan(mUiTaskQueue[i])) { + mUiTaskQueue.InsertElementAt(i, mozilla::Move(newTask)); + break; + } + } + + if (i == mUiTaskQueue.Length()) { + // We didn't insert the task, which means we should append it. + mUiTaskQueue.AppendElement(mozilla::Move(newTask)); + } + } + + if (i == 0) { + // if we're inserting it at the head of the queue, notify Java because + // we need to get a callback at an earlier time than the last scheduled + // callback + GeckoThread::RequestUiThreadCallback(int64_t(aDelayMs)); + } +} + +int64_t +AndroidBridge::RunDelayedUiThreadTasks() +{ + MutexAutoLock lock(mUiTaskQueueLock); + + while (!mUiTaskQueue.IsEmpty()) { + const int64_t timeLeft = mUiTaskQueue[0].MillisecondsToRunTime(); + if (timeLeft > 0) { + // this task (and therefore all remaining tasks) + // have not yet reached their runtime. return the + // time left until we should be called again + return timeLeft; + } + + // Retrieve task before unlocking/running. + RefPtr<Runnable> nextTask(mUiTaskQueue[0].TakeTask()); + mUiTaskQueue.RemoveElementAt(0); + + // Unlock to allow posting new tasks reentrantly. + MutexAutoUnlock unlock(mUiTaskQueueLock); + nextTask->Run(); + } + return -1; +} + +Object::LocalRef AndroidBridge::ChannelCreate(Object::Param stream) { + JNIEnv* const env = GetEnvForThread(); + auto rv = Object::LocalRef::Adopt(env, env->CallStaticObjectMethod( + sBridge->jChannels, sBridge->jChannelCreate, stream.Get())); + MOZ_CATCH_JNI_EXCEPTION(env); + return rv; +} + +void AndroidBridge::InputStreamClose(Object::Param obj) { + JNIEnv* const env = GetEnvForThread(); + env->CallVoidMethod(obj.Get(), sBridge->jClose); + MOZ_CATCH_JNI_EXCEPTION(env); +} + +uint32_t AndroidBridge::InputStreamAvailable(Object::Param obj) { + JNIEnv* const env = GetEnvForThread(); + auto rv = env->CallIntMethod(obj.Get(), sBridge->jAvailable); + MOZ_CATCH_JNI_EXCEPTION(env); + return rv; +} + +nsresult AndroidBridge::InputStreamRead(Object::Param obj, char *aBuf, uint32_t aCount, uint32_t *aRead) { + JNIEnv* const env = GetEnvForThread(); + auto arr = ByteBuffer::New(aBuf, aCount); + jint read = env->CallIntMethod(obj.Get(), sBridge->jByteBufferRead, arr.Get()); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + return NS_ERROR_FAILURE; + } + + if (read <= 0) { + *aRead = 0; + return NS_OK; + } + *aRead = read; + return NS_OK; +} |