/* -*- 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 #include #include #include #include "mozilla/layers/CompositorBridgeChild.h" #include "mozilla/layers/CompositorBridgeParent.h" #include "mozilla/Hal.h" #include "nsXULAppAPI.h" #include #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 "mozilla/dom/mobilemessage/PSms.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 "MediaPrefs.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 AndroidBridge::sStoragePaths; jclass AndroidBridge::GetClassGlobalRef(JNIEnv* env, const char* className) { // First try the default class loader. auto classRef = Class::LocalRef::Adopt( env, env->FindClass(className)); if (!classRef && sBridge && sBridge->mClassLoader) { // If the default class loader failed but we have an app class loader, try that. // Clear the pending exception from failed FindClass call above. env->ExceptionClear(); classRef = Class::LocalRef::Adopt(env, jclass( env->CallObjectMethod(sBridge->mClassLoader.Get(), sBridge->mClassLoaderLoadClass, StringParam(className, env).Get()))); } if (!classRef) { ALOG(">>> FATAL JNI ERROR! FindClass(className=\"%s\") failed. " "Did ProGuard optimize away something it shouldn't have?", className); env->ExceptionDescribe(); MOZ_CRASH(); } return Class::GlobalRef(env, classRef).Forget(); } 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() : mLayerClient(nullptr) , mUiTaskQueueLock("UiTaskQueue") { ALOG_BRIDGE("AndroidBridge::Init"); JNIEnv* const jEnv = jni::GetGeckoThreadEnv(); AutoLocalJNIFrame jniFrame(jEnv); mClassLoader = Object::GlobalRef(jEnv, java::GeckoThread::ClsLoader()); mClassLoaderLoadClass = GetMethodID( jEnv, jEnv->GetObjectClass(mClassLoader.Get()), "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); 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;"); #ifdef MOZ_WEBSMS_BACKEND AutoJNIClass smsMessage(jEnv, "android/telephony/SmsMessage"); mAndroidSmsMessageClass = smsMessage.getGlobalRef(); jCalculateLength = smsMessage.getStaticMethod("calculateLength", "(Ljava/lang/CharSequence;Z)[I"); #endif 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(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(aJNIEnv->GetObjectArrayElement(jArr, i)), aJNIEnv); nsJNIString isDefault( static_cast(aJNIEnv->GetObjectArrayElement(jArr, i + 1)), aJNIEnv); nsJNIString packageName( static_cast(aJNIEnv->GetObjectArrayElement(jArr, i + 2)), aJNIEnv); nsJNIString className( static_cast(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& 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(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(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(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); } void AndroidBridge::SetLayerClient(GeckoLayerClient::Param jobj) { mLayerClient = jobj; } 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(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 mMainThread; }; StaticRefPtr 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); } nsresult AndroidBridge::GetSegmentInfoForText(const nsAString& aText, nsIMobileMessageCallback* aRequest) { #ifndef MOZ_WEBSMS_BACKEND return NS_ERROR_FAILURE; #else ALOG_BRIDGE("AndroidBridge::GetSegmentInfoForText"); int32_t segments, charsPerSegment, charsAvailableInLastSegment; JNIEnv* const env = jni::GetGeckoThreadEnv(); AutoLocalJNIFrame jniFrame(env, 2); jstring jText = NewJavaString(&jniFrame, aText); jobject obj = env->CallStaticObjectMethod(mAndroidSmsMessageClass, jCalculateLength, jText, JNI_FALSE); if (jniFrame.CheckForException()) return NS_ERROR_FAILURE; jintArray arr = static_cast(obj); if (!arr || env->GetArrayLength(arr) != 4) return NS_ERROR_FAILURE; jint* info = env->GetIntArrayElements(arr, JNI_FALSE); segments = info[0]; // msgCount charsPerSegment = info[2]; // codeUnitsRemaining // segmentChars = (codeUnitCount + codeUnitsRemaining) / msgCount charsAvailableInLastSegment = (info[1] + info[2]) / info[0]; env->ReleaseIntArrayElements(arr, info, JNI_ABORT); // TODO Bug 908598 - Should properly use |QueueSmsRequest(...)| to queue up // the nsIMobileMessageCallback just like other functions. return aRequest->NotifySegmentInfoForTextGot(segments, charsPerSegment, charsAvailableInLastSegment); #endif } void AndroidBridge::SendMessage(const nsAString& aNumber, const nsAString& aMessage, nsIMobileMessageCallback* aRequest) { ALOG_BRIDGE("AndroidBridge::SendMessage"); uint32_t requestId; if (!QueueSmsRequest(aRequest, &requestId)) return; GeckoAppShell::SendMessage(aNumber, aMessage, requestId); } void AndroidBridge::GetMessage(int32_t aMessageId, nsIMobileMessageCallback* aRequest) { ALOG_BRIDGE("AndroidBridge::GetMessage"); uint32_t requestId; if (!QueueSmsRequest(aRequest, &requestId)) return; GeckoAppShell::GetMessage(aMessageId, requestId); } void AndroidBridge::DeleteMessage(int32_t aMessageId, nsIMobileMessageCallback* aRequest) { ALOG_BRIDGE("AndroidBridge::DeleteMessage"); uint32_t requestId; if (!QueueSmsRequest(aRequest, &requestId)) return; GeckoAppShell::DeleteMessage(aMessageId, requestId); } void AndroidBridge::MarkMessageRead(int32_t aMessageId, bool aValue, bool aSendReadReport, nsIMobileMessageCallback* aRequest) { ALOG_BRIDGE("AndroidBridge::MarkMessageRead"); uint32_t requestId; if (!QueueSmsRequest(aRequest, &requestId)) { return; } GeckoAppShell::MarkMessageRead(aMessageId, aValue, aSendReadReport, requestId); } NS_IMPL_ISUPPORTS0(MessageCursorContinueCallback) NS_IMETHODIMP MessageCursorContinueCallback::HandleContinue() { GeckoAppShell::GetNextMessage(mRequestId); return NS_OK; } already_AddRefed AndroidBridge::CreateMessageCursor(bool aHasStartDate, uint64_t aStartDate, bool aHasEndDate, uint64_t aEndDate, const char16_t** aNumbers, uint32_t aNumbersCount, const nsAString& aDelivery, bool aHasRead, bool aRead, bool aHasThreadId, uint64_t aThreadId, bool aReverse, nsIMobileMessageCursorCallback* aRequest) { ALOG_BRIDGE("AndroidBridge::CreateMessageCursor"); JNIEnv* const env = jni::GetGeckoThreadEnv(); uint32_t requestId; if (!QueueSmsCursorRequest(aRequest, &requestId)) return nullptr; AutoLocalJNIFrame jniFrame(env, 2); jobjectArray numbers = (jobjectArray)env->NewObjectArray(aNumbersCount, jStringClass, NewJavaString(&jniFrame, EmptyString())); for (uint32_t i = 0; i < aNumbersCount; ++i) { jstring elem = NewJavaString(&jniFrame, nsDependentString(aNumbers[i])); env->SetObjectArrayElement(numbers, i, elem); env->DeleteLocalRef(elem); } int64_t startDate = aHasStartDate ? aStartDate : -1; int64_t endDate = aHasEndDate ? aEndDate : -1; GeckoAppShell::CreateMessageCursor(startDate, endDate, ObjectArray::Ref::From(numbers), aNumbersCount, aDelivery, aHasRead, aRead, aHasThreadId, aThreadId, aReverse, requestId); nsCOMPtr callback = new MessageCursorContinueCallback(requestId); return callback.forget(); } NS_IMPL_ISUPPORTS0(ThreadCursorContinueCallback) NS_IMETHODIMP ThreadCursorContinueCallback::HandleContinue() { GeckoAppShell::GetNextThread(mRequestId); return NS_OK; } already_AddRefed AndroidBridge::CreateThreadCursor(nsIMobileMessageCursorCallback* aRequest) { ALOG_BRIDGE("AndroidBridge::CreateThreadCursor"); uint32_t requestId; if (!QueueSmsCursorRequest(aRequest, &requestId)) { return nullptr; } GeckoAppShell::CreateThreadCursor(requestId); nsCOMPtr callback = new ThreadCursorContinueCallback(requestId); return callback.forget(); } bool AndroidBridge::QueueSmsRequest(nsIMobileMessageCallback* aRequest, uint32_t* aRequestIdOut) { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); MOZ_ASSERT(aRequest && aRequestIdOut); const uint32_t length = mSmsRequests.Length(); for (uint32_t i = 0; i < length; i++) { if (!(mSmsRequests)[i]) { (mSmsRequests)[i] = aRequest; *aRequestIdOut = i; return true; } } mSmsRequests.AppendElement(aRequest); // After AppendElement(), previous `length` points to the new tail element. *aRequestIdOut = length; return true; } already_AddRefed AndroidBridge::DequeueSmsRequest(uint32_t aRequestId) { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); MOZ_ASSERT(aRequestId < mSmsRequests.Length()); if (aRequestId >= mSmsRequests.Length()) { return nullptr; } return mSmsRequests[aRequestId].forget(); } bool AndroidBridge::QueueSmsCursorRequest(nsIMobileMessageCursorCallback* aRequest, uint32_t* aRequestIdOut) { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); MOZ_ASSERT(aRequest && aRequestIdOut); const uint32_t length = mSmsCursorRequests.Length(); for (uint32_t i = 0; i < length; i++) { if (!(mSmsCursorRequests)[i]) { (mSmsCursorRequests)[i] = aRequest; *aRequestIdOut = i; return true; } } mSmsCursorRequests.AppendElement(aRequest); // After AppendElement(), previous `length` points to the new tail element. *aRequestIdOut = length; return true; } nsCOMPtr AndroidBridge::GetSmsCursorRequest(uint32_t aRequestId) { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); MOZ_ASSERT(aRequestId < mSmsCursorRequests.Length()); if (aRequestId >= mSmsCursorRequests.Length()) { return nullptr; } // TODO: remove on final dequeue return mSmsCursorRequests[aRequestId]; } already_AddRefed AndroidBridge::DequeueSmsCursorRequest(uint32_t aRequestId) { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); MOZ_ASSERT(aRequestId < mSmsCursorRequests.Length()); if (aRequestId >= mSmsCursorRequests.Length()) { return nullptr; } // TODO: remove on final dequeue return mSmsCursorRequests[aRequestId].forget(); } 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; } void AndroidBridge::SetFirstPaintViewport(const LayerIntPoint& aOffset, const CSSToLayerScale& aZoom, const CSSRect& aCssPageRect) { if (!mLayerClient) { return; } mLayerClient->SetFirstPaintViewport(float(aOffset.x), float(aOffset.y), aZoom.scale, aCssPageRect.x, aCssPageRect.y, aCssPageRect.XMost(), aCssPageRect.YMost()); } void AndroidBridge::SetPageRect(const CSSRect& aCssPageRect) { if (!mLayerClient) { return; } mLayerClient->SetPageRect(aCssPageRect.x, aCssPageRect.y, aCssPageRect.XMost(), aCssPageRect.YMost()); } void AndroidBridge::SyncViewportInfo(const LayerIntRect& aDisplayPort, const CSSToLayerScale& aDisplayResolution, bool aLayersUpdated, int32_t aPaintSyncId, ParentLayerRect& aScrollRect, CSSToParentLayerScale& aScale, ScreenMargin& aFixedLayerMargins) { if (!mLayerClient) { ALOG_BRIDGE("Exceptional Exit: %s", __PRETTY_FUNCTION__); return; } ViewTransform::LocalRef viewTransform = mLayerClient->SyncViewportInfo( aDisplayPort.x, aDisplayPort.y, aDisplayPort.width, aDisplayPort.height, aDisplayResolution.scale, aLayersUpdated, aPaintSyncId); MOZ_ASSERT(viewTransform, "No view transform object!"); aScrollRect = ParentLayerRect(viewTransform->X(), viewTransform->Y(), viewTransform->Width(), viewTransform->Height()); aScale.scale = viewTransform->Scale(); aFixedLayerMargins.top = viewTransform->FixedLayerMarginTop(); aFixedLayerMargins.right = viewTransform->FixedLayerMarginRight(); aFixedLayerMargins.bottom = viewTransform->FixedLayerMarginBottom(); aFixedLayerMargins.left = viewTransform->FixedLayerMarginLeft(); } void AndroidBridge::SyncFrameMetrics(const ParentLayerPoint& aScrollOffset, const CSSToParentLayerScale& aZoom, const CSSRect& aCssPageRect, const CSSRect& aDisplayPort, const CSSToLayerScale& aPaintedResolution, bool aLayersUpdated, int32_t aPaintSyncId, ScreenMargin& aFixedLayerMargins) { if (!mLayerClient) { ALOG_BRIDGE("Exceptional Exit: %s", __PRETTY_FUNCTION__); return; } // convert the displayport rect from document-relative CSS pixels to // document-relative device pixels LayerIntRect dp = gfx::RoundedToInt(aDisplayPort * aPaintedResolution); ViewTransform::LocalRef viewTransform = mLayerClient->SyncFrameMetrics( aScrollOffset.x, aScrollOffset.y, aZoom.scale, aCssPageRect.x, aCssPageRect.y, aCssPageRect.XMost(), aCssPageRect.YMost(), dp.x, dp.y, dp.width, dp.height, aPaintedResolution.scale, aLayersUpdated, aPaintSyncId); MOZ_ASSERT(viewTransform, "No view transform object!"); aFixedLayerMargins.top = viewTransform->FixedLayerMarginTop(); aFixedLayerMargins.right = viewTransform->FixedLayerMarginRight(); aFixedLayerMargins.bottom = viewTransform->FixedLayerMarginBottom(); aFixedLayerMargins.left = viewTransform->FixedLayerMarginLeft(); } /* 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() { AndroidBridge::Bridge()->ContentDocumentChanged(); return NS_OK; } NS_IMETHODIMP nsAndroidBridge::IsContentDocumentDisplayed(bool *aRet) { *aRet = AndroidBridge::Bridge()->IsContentDocumentDisplayed(); 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, "audio-playback")) { ALOG_BRIDGE("nsAndroidBridge::Observe, get audio-playback event."); nsCOMPtr window = do_QueryInterface(aSubject); MOZ_ASSERT(window); nsAutoString activeStr(aData); if (activeStr.EqualsLiteral("inactive-nonaudible")) { // This state means the audio becomes silent, but it's still playing, so // we don't need to notify the AudioFocusAgent. return NS_OK; } bool isPlaying = activeStr.EqualsLiteral("active"); UpdateAudioPlayingWindows(window, isPlaying); } return NS_OK; } void nsAndroidBridge::UpdateAudioPlayingWindows(nsPIDOMWindowOuter* aWindow, bool aPlaying) { // Request audio focus for the first audio playing window and abandon focus // for the last audio playing window. if (aPlaying && !mAudioPlayingWindows.Contains(aWindow)) { mAudioPlayingWindows.AppendElement(aWindow); if (mAudioPlayingWindows.Length() == 1) { ALOG_BRIDGE("nsAndroidBridge, request audio focus."); AudioFocusAgent::NotifyStartedPlaying(); } } else if (!aPlaying && mAudioPlayingWindows.Contains(aWindow)) { mAudioPlayingWindows.RemoveElement(aWindow); if (mAudioPlayingWindows.Length() == 0) { ALOG_BRIDGE("nsAndroidBridge, abandon audio focus."); AudioFocusAgent::NotifyStoppedPlaying(); } } } void nsAndroidBridge::AddObservers() { nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->AddObserver(this, "xpcom-shutdown", false); obs->AddObserver(this, "audio-playback", false); } } void nsAndroidBridge::RemoveObservers() { nsCOMPtr obs = mozilla::services::GetObserverService(); if (obs) { obs->RemoveObserver(this, "xpcom-shutdown"); obs->RemoveObserver(this, "audio-playback"); } } uint32_t AndroidBridge::GetScreenOrientation() { ALOG_BRIDGE("AndroidBridge::GetScreenOrientation"); int16_t orientation = GeckoAppShell::GetScreenOrientation(); if (!orientation) return dom::eScreenOrientation_None; return static_cast(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); bool AndroidBridge::GetThreadNameJavaProfiling(uint32_t aThreadId, nsCString & aResult) { auto jstrThreadName = GeckoJavaSampler::GetThreadName(aThreadId); if (!jstrThreadName) return false; aResult = jstrThreadName->ToCString(); return true; } bool AndroidBridge::GetFrameNameJavaProfiling(uint32_t aThreadId, uint32_t aSampleId, uint32_t aFrameId, nsCString & aResult) { auto jstrSampleName = GeckoJavaSampler::GetFrameName (aThreadId, aSampleId, aFrameId); if (!jstrSampleName) return false; aResult = jstrSampleName->ToCString(); return true; } void AndroidBridge::ContentDocumentChanged() { if (!mLayerClient) { return; } mLayerClient->ContentDocumentChanged(); } bool AndroidBridge::IsContentDocumentDisplayed() { if (!mLayerClient) return false; return mLayerClient->IsContentDocumentDisplayed(); } class AndroidBridge::DelayedTask { using TimeStamp = mozilla::TimeStamp; using TimeDuration = mozilla::TimeDuration; public: DelayedTask(already_AddRefed aTask) : mTask(aTask) , mRunTime() // Null timestamp representing no delay. {} DelayedTask(already_AddRefed 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 TakeTask() { return mTask.forget(); } private: RefPtr mTask; const TimeStamp mRunTime; }; void AndroidBridge::PostTaskToUiThread(already_AddRefed 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 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; } nsresult AndroidBridge::GetExternalPublicDirectory(const nsAString& aType, nsAString& aPath) { if (XRE_IsContentProcess()) { nsString key(aType); nsAutoString path; if (AndroidBridge::sStoragePaths.Get(key, &path)) { aPath = path; return NS_OK; } // Lazily get the value from the parent. dom::ContentChild* child = dom::ContentChild::GetSingleton(); if (child) { nsAutoString type(aType); child->SendGetDeviceStorageLocation(type, &path); if (!path.IsEmpty()) { AndroidBridge::sStoragePaths.Put(key, path); aPath = path; return NS_OK; } } ALOG_BRIDGE("AndroidBridge::GetExternalPublicDirectory no cache for %s", NS_ConvertUTF16toUTF8(aType).get()); return NS_ERROR_NOT_AVAILABLE; } auto path = GeckoAppShell::GetExternalPublicDirectory(aType); if (!path) { return NS_ERROR_NOT_AVAILABLE; } aPath = path->ToString(); return NS_OK; }