forked from mirrors/gecko-dev
		
	 a628865b23
			
		
	
	
		a628865b23
		
	
	
	
	
		
			
			Actually, we can get Android release version from JNI generator, so it is unnecessary to use `AndroidBridge` to get it. Also, this cleans up unused AndroidBridge functions. Differential Revision: https://phabricator.services.mozilla.com/D164431
		
			
				
	
	
		
			431 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			431 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: c++; c-basic-offset: 2; 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 "AndroidBridge.h"
 | |
| #include "AndroidBridgeUtilities.h"
 | |
| #include "nsAlertsUtils.h"
 | |
| #include "nsAppShell.h"
 | |
| #include "nsOSHelperAppService.h"
 | |
| #include "nsWindow.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsPresContext.h"
 | |
| #include "nsPIDOMWindow.h"
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "mozilla/Mutex.h"
 | |
| #include "nsPrintfCString.h"
 | |
| #include "nsContentUtils.h"
 | |
| 
 | |
| #include "EventDispatcher.h"
 | |
| 
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "mozilla/UniquePtr.h"
 | |
| #include "WidgetUtils.h"
 | |
| 
 | |
| #include "mozilla/java/EventDispatcherWrappers.h"
 | |
| #include "mozilla/java/GeckoAppShellWrappers.h"
 | |
| #include "mozilla/java/GeckoThreadWrappers.h"
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| AndroidBridge* AndroidBridge::sBridge = nullptr;
 | |
| static jobject sGlobalContext = nullptr;
 | |
| nsTHashMap<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(const_cast<char*>("NSS_DISABLE_UNLOAD=1"));
 | |
| 
 | |
|   MOZ_ASSERT(!sBridge);
 | |
|   sBridge = new AndroidBridge();
 | |
| }
 | |
| 
 | |
| 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() {
 | |
|   ALOG_BRIDGE("AndroidBridge::Init");
 | |
| 
 | |
|   JNIEnv* const jEnv = jni::GetGeckoThreadEnv();
 | |
|   AutoLocalJNIFrame jniFrame(jEnv);
 | |
| 
 | |
|   mMessageQueue = java::GeckoThread::MsgQueue();
 | |
|   auto msgQueueClass = jni::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;");
 | |
| }
 | |
| 
 | |
| 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;
 | |
|     }
 | |
|     java::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);
 | |
| 
 | |
|   java::GeckoAppShell::Vibrate(jni::LongArray::Ref::From(array),
 | |
|                                -1 /* don't repeat */);
 | |
| }
 | |
| 
 | |
| 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 = java::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);
 | |
| }
 | |
| 
 | |
| namespace mozilla {
 | |
| class TracerRunnable : public Runnable {
 | |
|  public:
 | |
|   TracerRunnable() : Runnable("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();
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | |
| 
 | |
| 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 = java::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::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 = java::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() {
 | |
|   // The context object can change, so get a fresh copy every time.
 | |
|   auto context = java::GeckoAppShell::GetApplicationContext();
 | |
|   sGlobalContext = jni::Object::GlobalRef(context).Forget();
 | |
|   MOZ_ASSERT(sGlobalContext);
 | |
|   return sGlobalContext;
 | |
| }
 | |
| 
 | |
| /* Implementation file */
 | |
| NS_IMPL_ISUPPORTS(nsAndroidBridge, nsIAndroidEventDispatcher, nsIAndroidBridge)
 | |
| 
 | |
| nsAndroidBridge::nsAndroidBridge() {
 | |
|   if (jni::IsAvailable()) {
 | |
|     RefPtr<widget::EventDispatcher> dispatcher = new widget::EventDispatcher();
 | |
|     dispatcher->Attach(java::EventDispatcher::GetInstance(),
 | |
|                        /* window */ nullptr);
 | |
|     mEventDispatcher = dispatcher;
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsAndroidBridge::GetDispatcherByName(const char* aName,
 | |
|                                      nsIAndroidEventDispatcher** aResult) {
 | |
|   if (!jni::IsAvailable()) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   RefPtr<widget::EventDispatcher> dispatcher = new widget::EventDispatcher();
 | |
|   dispatcher->Attach(java::EventDispatcher::ByName(aName),
 | |
|                      /* window */ nullptr);
 | |
|   dispatcher.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsAndroidBridge::~nsAndroidBridge() {}
 | |
| 
 | |
| hal::ScreenOrientation AndroidBridge::GetScreenOrientation() {
 | |
|   ALOG_BRIDGE("AndroidBridge::GetScreenOrientation");
 | |
| 
 | |
|   int16_t orientation = java::GeckoAppShell::GetScreenOrientation();
 | |
| 
 | |
|   return hal::ScreenOrientation(orientation);
 | |
| }
 | |
| 
 | |
| uint16_t AndroidBridge::GetScreenAngle() {
 | |
|   return java::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 =
 | |
|       java::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 = jni::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 = jni::Object::LocalRef::Adopt(
 | |
|       env, env->CallObjectMethod(mMessageQueue.Get(), mMessageQueueNext));
 | |
|   if (!msg) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return java::GeckoThread::PumpMessageLoop(msg);
 | |
| }
 |