/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Android code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Vladimir Vukicevic * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include #include "mozilla/Hal.h" #include "nsXULAppAPI.h" #include #include "nsXPCOMStrings.h" #include "AndroidBridge.h" #include "nsAppShell.h" #include "nsOSHelperAppService.h" #include "nsWindow.h" #include "mozilla/Preferences.h" #include "nsThreadUtils.h" #include "nsIThreadManager.h" #include "mozilla/dom/sms/PSms.h" #include "gfxImageSurface.h" #include "gfxContext.h" #include "nsPresContext.h" #include "nsIDocShell.h" #include "nsPIDOMWindow.h" #ifdef DEBUG #define ALOG_BRIDGE(args...) ALOG(args) #else #define ALOG_BRIDGE(args...) #endif #define IME_FULLSCREEN_PREF "widget.ime.android.landscape_fullscreen" #define IME_FULLSCREEN_THRESHOLD_PREF "widget.ime.android.fullscreen_threshold" using namespace mozilla; AndroidBridge *AndroidBridge::sBridge = 0; AndroidBridge * AndroidBridge::ConstructBridge(JNIEnv *jEnv, jclass jGeckoAppShellClass) { /* 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"); sBridge = new AndroidBridge(); if (!sBridge->Init(jEnv, jGeckoAppShellClass)) { delete sBridge; sBridge = 0; } return sBridge; } bool AndroidBridge::Init(JNIEnv *jEnv, jclass jGeckoAppShellClass) { ALOG_BRIDGE("AndroidBridge::Init"); jEnv->GetJavaVM(&mJavaVM); mJNIEnv = nsnull; mThread = nsnull; mOpenedGraphicsLibraries = false; mHasNativeBitmapAccess = false; mHasNativeWindowAccess = false; mGeckoAppShellClass = (jclass) jEnv->NewGlobalRef(jGeckoAppShellClass); jNotifyIME = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIME", "(II)V"); jNotifyIMEEnabled = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEEnabled", "(ILjava/lang/String;Ljava/lang/String;Z)V"); jNotifyIMEChange = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEChange", "(Ljava/lang/String;III)V"); jNotifyScreenShot = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyScreenShot", "(Ljava/nio/ByteBuffer;III)V"); jAcknowledgeEventSync = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "acknowledgeEventSync", "()V"); jEnableDeviceMotion = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableDeviceMotion", "(Z)V"); jEnableLocation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableLocation", "(Z)V"); jEnableSensor = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableSensor", "(I)V"); jDisableSensor = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableSensor", "(I)V"); jReturnIMEQueryResult = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "returnIMEQueryResult", "(Ljava/lang/String;II)V"); jScheduleRestart = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "scheduleRestart", "()V"); jNotifyXreExit = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "onXreExit", "()V"); jGetHandlersForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForMimeType", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"); jGetHandlersForURL = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForURL", "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"); jOpenUriExternal = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "openUriExternal", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z"); jGetMimeTypeFromExtensions = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getMimeTypeFromExtensions", "(Ljava/lang/String;)Ljava/lang/String;"); jGetExtensionFromMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getExtensionFromMimeType", "(Ljava/lang/String;)Ljava/lang/String;"); jMoveTaskToBack = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "moveTaskToBack", "()V"); jGetClipboardText = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getClipboardText", "()Ljava/lang/String;"); jSetClipboardText = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setClipboardText", "(Ljava/lang/String;)V"); jShowAlertNotification = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); jShowFilePicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePicker", "(Ljava/lang/String;)Ljava/lang/String;"); jAlertsProgressListener_OnProgress = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V"); jAlertsProgressListener_OnCancel = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnCancel", "(Ljava/lang/String;)V"); jGetDpi = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getDpi", "()I"); jSetFullScreen = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setFullScreen", "(Z)V"); jShowInputMethodPicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showInputMethodPicker", "()V"); jSetPreventPanning = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setPreventPanning", "(Z)V"); jHideProgressDialog = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideProgressDialog", "()V"); jPerformHapticFeedback = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "performHapticFeedback", "(Z)V"); jVibrate1 = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "(J)V"); jVibrateA = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "vibrate", "([JI)V"); jCancelVibrate = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "cancelVibrate", "()V"); jSetKeepScreenOn = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setKeepScreenOn", "(Z)V"); jIsNetworkLinkUp = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkUp", "()Z"); jIsNetworkLinkKnown = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isNetworkLinkKnown", "()Z"); jSetSelectedLocale = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setSelectedLocale", "(Ljava/lang/String;)V"); jScanMedia = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "scanMedia", "(Ljava/lang/String;Ljava/lang/String;)V"); jGetSystemColors = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getSystemColors", "()[I"); jGetIconForExtension = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getIconForExtension", "(Ljava/lang/String;I)[B"); jFireAndWaitForTracerEvent = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "fireAndWaitForTracerEvent", "()V"); jCreateShortcut = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "createShortcut", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); jGetShowPasswordSetting = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getShowPasswordSetting", "()Z"); jPostToJavaThread = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "postToJavaThread", "(Z)V"); jInitCamera = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "initCamera", "(Ljava/lang/String;III)[I"); jCloseCamera = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "closeCamera", "()V"); jIsTablet = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "isTablet", "()Z"); jEnableBatteryNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableBatteryNotifications", "()V"); jDisableBatteryNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableBatteryNotifications", "()V"); jGetCurrentBatteryInformation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getCurrentBatteryInformation", "()[D"); jGetAccessibilityEnabled = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getAccessibilityEnabled", "()Z"); jHandleGeckoMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "handleGeckoMessage", "(Ljava/lang/String;)Ljava/lang/String;"); jCheckUriVisited = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "checkUriVisited", "(Ljava/lang/String;)V"); jMarkUriVisited = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "markUriVisited", "(Ljava/lang/String;)V"); jNumberOfMessages = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getNumberOfMessagesForText", "(Ljava/lang/String;)I"); jSendMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "sendMessage", "(Ljava/lang/String;Ljava/lang/String;IJ)V"); jSaveSentMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "saveSentMessage", "(Ljava/lang/String;Ljava/lang/String;J)I"); jGetMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getMessage", "(IIJ)V"); jDeleteMessage = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "deleteMessage", "(IIJ)V"); jCreateMessageList = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "createMessageList", "(JJ[Ljava/lang/String;IIZIJ)V"); jGetNextMessageinList = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getNextMessageInList", "(IIJ)V"); jClearMessageList = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "clearMessageList", "(I)V"); jGetCurrentNetworkInformation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getCurrentNetworkInformation", "()[D"); jEnableNetworkNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableNetworkNotifications", "()V"); jDisableNetworkNotifications = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "disableNetworkNotifications", "()V"); jEGLContextClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGLContext")); jEGL10Class = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("javax/microedition/khronos/egl/EGL10")); jEGLSurfaceImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLSurfaceImpl")); jEGLContextImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLContextImpl")); jEGLConfigImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLConfigImpl")); jEGLDisplayImplClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("com/google/android/gles_jni/EGLDisplayImpl")); InitAndroidJavaWrappers(jEnv); // jEnv should NOT be cached here by anything -- the jEnv here // is not valid for the real gecko main thread, which is set // at SetMainThread time. return true; } bool AndroidBridge::SetMainThread(void *thr) { ALOG_BRIDGE("AndroidBridge::SetMainThread"); if (thr) { mThread = thr; mJavaVM->GetEnv((void**) &mJNIEnv, JNI_VERSION_1_2); return (bool) mJNIEnv; } mJNIEnv = nsnull; mThread = nsnull; return true; } void AndroidBridge::NotifyIME(int aType, int aState) { ALOG_BRIDGE("AndroidBridge::NotifyIME"); JNIEnv *env = AndroidBridge::GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(sBridge->mGeckoAppShellClass, sBridge->jNotifyIME, aType, aState); } void AndroidBridge::NotifyIMEEnabled(int aState, const nsAString& aTypeHint, const nsAString& aActionHint) { ALOG_BRIDGE("AndroidBridge::NotifyIMEEnabled"); if (!sBridge) return; JNIEnv *env = AndroidBridge::GetJNIEnv(); if (!env) return; nsPromiseFlatString typeHint(aTypeHint); nsPromiseFlatString actionHint(aActionHint); jvalue args[4]; AutoLocalJNIFrame jniFrame(env, 1); args[0].i = aState; args[1].l = env->NewString(typeHint.get(), typeHint.Length()); args[2].l = env->NewString(actionHint.get(), actionHint.Length()); args[3].z = false; PRInt32 landscapeFS; if (NS_SUCCEEDED(Preferences::GetInt(IME_FULLSCREEN_PREF, &landscapeFS))) { if (landscapeFS == 1) { args[3].z = true; } else if (landscapeFS == -1){ if (NS_SUCCEEDED( Preferences::GetInt(IME_FULLSCREEN_THRESHOLD_PREF, &landscapeFS))) { // the threshold is hundreths of inches, so convert the // threshold to pixels and multiply the height by 100 if (nsWindow::GetAndroidScreenBounds().height * 100 < landscapeFS * Bridge()->GetDPI()) { args[3].z = true; } } } } env->CallStaticVoidMethodA(sBridge->mGeckoAppShellClass, sBridge->jNotifyIMEEnabled, args); } void AndroidBridge::NotifyIMEChange(const PRUnichar *aText, PRUint32 aTextLen, int aStart, int aEnd, int aNewEnd) { ALOG_BRIDGE("AndroidBridge::NotifyIMEChange"); if (!sBridge) { return; } JNIEnv *env = AndroidBridge::GetJNIEnv(); if (!env) return; jvalue args[4]; AutoLocalJNIFrame jniFrame(env, 1); args[0].l = env->NewString(aText, aTextLen); args[1].i = aStart; args[2].i = aEnd; args[3].i = aNewEnd; env->CallStaticVoidMethodA(sBridge->mGeckoAppShellClass, sBridge->jNotifyIMEChange, args); } void AndroidBridge::AcknowledgeEventSync() { ALOG_BRIDGE("AndroidBridge::AcknowledgeEventSync"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jAcknowledgeEventSync); } void AndroidBridge::EnableDeviceMotion(bool aEnable) { ALOG_BRIDGE("AndroidBridge::EnableDeviceMotion"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jEnableDeviceMotion, aEnable); } void AndroidBridge::EnableLocation(bool aEnable) { ALOG_BRIDGE("AndroidBridge::EnableLocation"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jEnableLocation, aEnable); } void AndroidBridge::EnableSensor(int aSensorType) { ALOG_BRIDGE("AndroidBridge::EnableSensor"); mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jEnableSensor, aSensorType); } void AndroidBridge::DisableSensor(int aSensorType) { ALOG_BRIDGE("AndroidBridge::DisableSensor"); mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jDisableSensor, aSensorType); } void AndroidBridge::ReturnIMEQueryResult(const PRUnichar *aResult, PRUint32 aLen, int aSelStart, int aSelLen) { ALOG_BRIDGE("AndroidBridge::ReturnIMEQueryResult"); JNIEnv *env = GetJNIEnv(); if (!env) return; jvalue args[3]; AutoLocalJNIFrame jniFrame(env, 1); args[0].l = env->NewString(aResult, aLen); args[1].i = aSelStart; args[2].i = aSelLen; env->CallStaticVoidMethodA(mGeckoAppShellClass, jReturnIMEQueryResult, args); } void AndroidBridge::ScheduleRestart() { ALOG_BRIDGE("scheduling reboot"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jScheduleRestart); } void AndroidBridge::NotifyXreExit() { ALOG_BRIDGE("xre exiting"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jNotifyXreExit); } 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) { nsJNIString name( static_cast(aJNIEnv->GetObjectArrayElement(jArr, i))); nsJNIString isDefault( static_cast(aJNIEnv->GetObjectArrayElement(jArr, i + 1))); nsJNIString packageName( static_cast(aJNIEnv->GetObjectArrayElement(jArr, i + 2))); nsJNIString className( static_cast(aJNIEnv->GetObjectArrayElement(jArr, i + 3))); 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 char *aMimeType, nsIMutableArray *aHandlersArray, nsIHandlerApp **aDefaultApp, const nsAString& aAction) { ALOG_BRIDGE("AndroidBridge::GetHandlersForMimeType"); JNIEnv *env = GetJNIEnv(); if (!env) return false; AutoLocalJNIFrame jniFrame(env); NS_ConvertUTF8toUTF16 wMimeType(aMimeType); jstring jstrMimeType = env->NewString(wMimeType.get(), wMimeType.Length()); jstring jstrAction = env->NewString(nsPromiseFlatString(aAction).get(), aAction.Length()); jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetHandlersForMimeType, jstrMimeType, jstrAction); jobjectArray arr = static_cast(obj); if (!arr) return false; jsize len = env->GetArrayLength(arr); if (!aHandlersArray) return len > 0; getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp, aAction, nsDependentCString(aMimeType)); return true; } bool AndroidBridge::GetHandlersForURL(const char *aURL, nsIMutableArray* aHandlersArray, nsIHandlerApp **aDefaultApp, const nsAString& aAction) { ALOG_BRIDGE("AndroidBridge::GetHandlersForURL"); JNIEnv *env = GetJNIEnv(); if (!env) return false; AutoLocalJNIFrame jniFrame(env); NS_ConvertUTF8toUTF16 wScheme(aURL); jstring jstrScheme = env->NewString(wScheme.get(), wScheme.Length()); jstring jstrAction = env->NewString(nsPromiseFlatString(aAction).get(), aAction.Length()); jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetHandlersForURL, jstrScheme, jstrAction); jobjectArray arr = static_cast(obj); if (!arr) return false; jsize len = env->GetArrayLength(arr); if (!aHandlersArray) return len > 0; getHandlersFromStringArray(env, arr, len, aHandlersArray, aDefaultApp, aAction); return true; } bool AndroidBridge::OpenUriExternal(const nsACString& aUriSpec, const nsACString& aMimeType, const nsAString& aPackageName, const nsAString& aClassName, const nsAString& aAction, const nsAString& aTitle) { ALOG_BRIDGE("AndroidBridge::OpenUriExternal"); JNIEnv *env = GetJNIEnv(); if (!env) return false; AutoLocalJNIFrame jniFrame(env); NS_ConvertUTF8toUTF16 wUriSpec(aUriSpec); NS_ConvertUTF8toUTF16 wMimeType(aMimeType); jstring jstrUri = env->NewString(wUriSpec.get(), wUriSpec.Length()); jstring jstrType = env->NewString(wMimeType.get(), wMimeType.Length()); jstring jstrPackage = env->NewString(nsPromiseFlatString(aPackageName).get(), aPackageName.Length()); jstring jstrClass = env->NewString(nsPromiseFlatString(aClassName).get(), aClassName.Length()); jstring jstrAction = env->NewString(nsPromiseFlatString(aAction).get(), aAction.Length()); jstring jstrTitle = env->NewString(nsPromiseFlatString(aTitle).get(), aTitle.Length()); return env->CallStaticBooleanMethod(mGeckoAppShellClass, jOpenUriExternal, jstrUri, jstrType, jstrPackage, jstrClass, jstrAction, jstrTitle); } void AndroidBridge::GetMimeTypeFromExtensions(const nsACString& aFileExt, nsCString& aMimeType) { ALOG_BRIDGE("AndroidBridge::GetMimeTypeFromExtensions"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); NS_ConvertUTF8toUTF16 wFileExt(aFileExt); jstring jstrExt = env->NewString(wFileExt.get(), wFileExt.Length()); jstring jstrType = static_cast( env->CallStaticObjectMethod(mGeckoAppShellClass, jGetMimeTypeFromExtensions, jstrExt)); nsJNIString jniStr(jstrType); aMimeType.Assign(NS_ConvertUTF16toUTF8(jniStr.get())); } void AndroidBridge::GetExtensionFromMimeType(const nsACString& aMimeType, nsACString& aFileExt) { ALOG_BRIDGE("AndroidBridge::GetExtensionFromMimeType"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); NS_ConvertUTF8toUTF16 wMimeType(aMimeType); jstring jstrType = env->NewString(wMimeType.get(), wMimeType.Length()); jstring jstrExt = static_cast( env->CallStaticObjectMethod(mGeckoAppShellClass, jGetExtensionFromMimeType, jstrType)); nsJNIString jniStr(jstrExt); aFileExt.Assign(NS_ConvertUTF16toUTF8(jniStr.get())); } void AndroidBridge::MoveTaskToBack() { ALOG_BRIDGE("AndroidBridge::MoveTaskToBack"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jMoveTaskToBack); } bool AndroidBridge::GetClipboardText(nsAString& aText) { ALOG_BRIDGE("AndroidBridge::GetClipboardText"); JNIEnv *env = GetJNIEnv(); if (!env) return false; jstring jstrType = static_cast(env-> CallStaticObjectMethod(mGeckoAppShellClass, jGetClipboardText)); if (!jstrType) return false; nsJNIString jniStr(jstrType); aText.Assign(jniStr); return true; } void AndroidBridge::SetClipboardText(const nsAString& aText) { ALOG_BRIDGE("AndroidBridge::SetClipboardText"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jstr = env->NewString(nsPromiseFlatString(aText).get(), aText.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jSetClipboardText, jstr); } bool AndroidBridge::ClipboardHasText() { ALOG_BRIDGE("AndroidBridge::ClipboardHasText"); JNIEnv *env = GetJNIEnv(); if (!env) return false; jstring jstrType = static_cast(env-> CallStaticObjectMethod(mGeckoAppShellClass, jGetClipboardText)); if (!jstrType) return false; return true; } void AndroidBridge::EmptyClipboard() { ALOG_BRIDGE("AndroidBridge::EmptyClipboard"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jSetClipboardText, nsnull); } void AndroidBridge::ShowAlertNotification(const nsAString& aImageUrl, const nsAString& aAlertTitle, const nsAString& aAlertText, const nsAString& aAlertCookie, nsIObserver *aAlertListener, const nsAString& aAlertName) { ALOG_BRIDGE("ShowAlertNotification"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); if (nsAppShell::gAppShell && aAlertListener) nsAppShell::gAppShell->AddObserver(aAlertName, aAlertListener); jvalue args[5]; args[0].l = env->NewString(nsPromiseFlatString(aImageUrl).get(), aImageUrl.Length()); args[1].l = env->NewString(nsPromiseFlatString(aAlertTitle).get(), aAlertTitle.Length()); args[2].l = env->NewString(nsPromiseFlatString(aAlertText).get(), aAlertText.Length()); args[3].l = env->NewString(nsPromiseFlatString(aAlertCookie).get(), aAlertCookie.Length()); args[4].l = env->NewString(nsPromiseFlatString(aAlertName).get(), aAlertName.Length()); env->CallStaticVoidMethodA(mGeckoAppShellClass, jShowAlertNotification, args); } void AndroidBridge::AlertsProgressListener_OnProgress(const nsAString& aAlertName, PRInt64 aProgress, PRInt64 aProgressMax, const nsAString& aAlertText) { ALOG_BRIDGE("AlertsProgressListener_OnProgress"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jstrName = env->NewString(nsPromiseFlatString(aAlertName).get(), aAlertName.Length()); jstring jstrText = env->NewString(nsPromiseFlatString(aAlertText).get(), aAlertText.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jAlertsProgressListener_OnProgress, jstrName, aProgress, aProgressMax, jstrText); } void AndroidBridge::AlertsProgressListener_OnCancel(const nsAString& aAlertName) { ALOG_BRIDGE("AlertsProgressListener_OnCancel"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jstrName = env->NewString(nsPromiseFlatString(aAlertName).get(), aAlertName.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jAlertsProgressListener_OnCancel, jstrName); } int AndroidBridge::GetDPI() { ALOG_BRIDGE("AndroidBridge::GetDPI"); JNIEnv *env = GetJNIEnv(); if (!env) return 72; // what is a better value. If we don't have a env here, we are hosed. return (int) env->CallStaticIntMethod(mGeckoAppShellClass, jGetDpi); } void AndroidBridge::ShowFilePicker(nsAString& aFilePath, nsAString& aFilters) { ALOG_BRIDGE("AndroidBridge::ShowFilePicker"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jstrFilers = env->NewString(nsPromiseFlatString(aFilters).get(), aFilters.Length()); jstring jstr = static_cast(env->CallStaticObjectMethod( mGeckoAppShellClass, jShowFilePicker, jstrFilers)); aFilePath.Assign(nsJNIString(jstr)); } void AndroidBridge::SetFullScreen(bool aFullScreen) { ALOG_BRIDGE("AndroidBridge::SetFullScreen"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jSetFullScreen, aFullScreen); } void AndroidBridge::HideProgressDialogOnce() { static bool once = false; JNIEnv *env = GetJNIEnv(); if (!env) return; if (!once) { ALOG_BRIDGE("AndroidBridge::HideProgressDialogOnce"); env->CallStaticVoidMethod(mGeckoAppShellClass, jHideProgressDialog); once = true; } } bool AndroidBridge::GetAccessibilityEnabled() { ALOG_BRIDGE("AndroidBridge::GetAccessibilityEnabled"); JNIEnv *env = GetJNIEnv(); if (!env) return false; return env->CallStaticBooleanMethod(mGeckoAppShellClass, jGetAccessibilityEnabled); } void AndroidBridge::PerformHapticFeedback(bool aIsLongPress) { ALOG_BRIDGE("AndroidBridge::PerformHapticFeedback"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jPerformHapticFeedback, aIsLongPress); } void AndroidBridge::Vibrate(const nsTArray& aPattern) { JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame frame; ALOG_BRIDGE("AndroidBridge::Vibrate"); PRUint32 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; } env->CallStaticVoidMethod(mGeckoAppShellClass, jVibrate1, d); return; } // First element of the array vibrate() expects is how long to wait // *before* vibrating. For us, this is always 0. jlongArray array = env->NewLongArray(len + 1); if (!array) { ALOG_BRIDGE(" failed to allocate array"); return; } jlong* elts = env->GetLongArrayElements(array, nsnull); elts[0] = 0; for (PRUint32 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); env->CallStaticVoidMethod(mGeckoAppShellClass, jVibrateA, array, -1/*don't repeat*/); // GC owns |array| now? } void AndroidBridge::CancelVibrate() { JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jCancelVibrate); } bool AndroidBridge::IsNetworkLinkUp() { ALOG_BRIDGE("AndroidBridge::IsNetworkLinkUp"); JNIEnv *env = GetJNIEnv(); if (!env) return false; return !!env->CallStaticBooleanMethod(mGeckoAppShellClass, jIsNetworkLinkUp); } bool AndroidBridge::IsNetworkLinkKnown() { ALOG_BRIDGE("AndroidBridge::IsNetworkLinkKnown"); JNIEnv *env = GetJNIEnv(); if (!env) return false; return !!env->CallStaticBooleanMethod(mGeckoAppShellClass, jIsNetworkLinkKnown); } void AndroidBridge::SetSelectedLocale(const nsAString& aLocale) { ALOG_BRIDGE("AndroidBridge::SetSelectedLocale"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jLocale = env->NewString(PromiseFlatString(aLocale).get(), aLocale.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jSetSelectedLocale, jLocale); } void AndroidBridge::GetSystemColors(AndroidSystemColors *aColors) { ALOG_BRIDGE("AndroidBridge::GetSystemColors"); NS_ASSERTION(aColors != nsnull, "AndroidBridge::GetSystemColors: aColors is null!"); if (!aColors) return; JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetSystemColors); jintArray arr = static_cast(obj); if (!arr) return; jsize len = env->GetArrayLength(arr); jint *elements = env->GetIntArrayElements(arr, 0); PRUint32 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 (PRUint32 i = 0; i < colorsCount; i++) { PRUint32 androidColor = static_cast(elements[i]); PRUint8 r = (androidColor & 0x00ff0000) >> 16; PRUint8 b = (androidColor & 0x000000ff); colors[i] = androidColor & 0xff00ff00 | b << 16 | r; } env->ReleaseIntArrayElements(arr, elements, 0); } void AndroidBridge::GetIconForExtension(const nsACString& aFileExt, PRUint32 aIconSize, PRUint8 * const aBuf) { ALOG_BRIDGE("AndroidBridge::GetIconForExtension"); NS_ASSERTION(aBuf != nsnull, "AndroidBridge::GetIconForExtension: aBuf is null!"); if (!aBuf) return; JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); nsString fileExt; CopyUTF8toUTF16(aFileExt, fileExt); jstring jstrFileExt = env->NewString(nsPromiseFlatString(fileExt).get(), fileExt.Length()); jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetIconForExtension, jstrFileExt, aIconSize); jbyteArray arr = static_cast(obj); NS_ASSERTION(arr != nsnull, "AndroidBridge::GetIconForExtension: Returned pixels array is null!"); if (!arr) return; jsize len = env->GetArrayLength(arr); jbyte *elements = env->GetByteArrayElements(arr, 0); PRUint32 bufSize = aIconSize * aIconSize * 4; NS_ASSERTION(len == bufSize, "AndroidBridge::GetIconForExtension: Pixels array is incomplete!"); if (len == bufSize) memcpy(aBuf, elements, bufSize); env->ReleaseByteArrayElements(arr, elements, 0); } bool AndroidBridge::GetShowPasswordSetting() { ALOG_BRIDGE("AndroidBridge::GetShowPasswordSetting"); JNIEnv *env = GetJNIEnv(); if (!env) return false; return env->CallStaticBooleanMethod(mGeckoAppShellClass, jGetShowPasswordSetting); } void AndroidBridge::SetSurfaceView(jobject obj) { mSurfaceView.Init(obj); } void AndroidBridge::SetSoftwareLayerClient(jobject obj) { mSoftwareLayerClient.Init(obj); } void AndroidBridge::ShowInputMethodPicker() { ALOG_BRIDGE("AndroidBridge::ShowInputMethodPicker"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jShowInputMethodPicker); } void * AndroidBridge::CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView &sview) { ALOG_BRIDGE("AndroidBridge::CallEglCreateWindowSurface"); JNIEnv *env = GetJNIEnv(); if (!env) return NULL; AutoLocalJNIFrame jniFrame(env); /* * This is basically: * * s = EGLContext.getEGL().eglCreateWindowSurface(new EGLDisplayImpl(dpy), * new EGLConfigImpl(config), * view.getHolder(), null); * return s.mEGLSurface; * * We can't do it from java, because the EGLConfigImpl constructor is private. */ jobject surfaceHolder = sview.GetSurfaceHolder(); if (!surfaceHolder) return nsnull; // grab some fields and methods we'll need jmethodID constructConfig = env->GetMethodID(jEGLConfigImplClass, "", "(I)V"); jmethodID constructDisplay = env->GetMethodID(jEGLDisplayImplClass, "", "(I)V"); jmethodID getEgl = env->GetStaticMethodID(jEGLContextClass, "getEGL", "()Ljavax/microedition/khronos/egl/EGL;"); jmethodID createWindowSurface = env->GetMethodID(jEGL10Class, "eglCreateWindowSurface", "(Ljavax/microedition/khronos/egl/EGLDisplay;Ljavax/microedition/khronos/egl/EGLConfig;Ljava/lang/Object;[I)Ljavax/microedition/khronos/egl/EGLSurface;"); jobject egl = env->CallStaticObjectMethod(jEGLContextClass, getEgl); jobject jdpy = env->NewObject(jEGLDisplayImplClass, constructDisplay, (int) dpy); jobject jconf = env->NewObject(jEGLConfigImplClass, constructConfig, (int) config); // make the call jobject surf = env->CallObjectMethod(egl, createWindowSurface, jdpy, jconf, surfaceHolder, NULL); if (!surf) return nsnull; jfieldID sfield = env->GetFieldID(jEGLSurfaceImplClass, "mEGLSurface", "I"); jint realSurface = env->GetIntField(surf, sfield); return (void*) realSurface; } bool AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt) { ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName); JNIEnv *env = GetJNIEnv(); if (!env) return false; AutoLocalJNIFrame jniFrame(env, 3); jclass cls = env->FindClass(className); if (!cls) return false; jfieldID field = env->GetStaticFieldID(cls, fieldName, "I"); if (!field) return false; *aInt = static_cast(env->GetStaticIntField(cls, field)); return true; } bool AndroidBridge::GetStaticStringField(const char *className, const char *fieldName, nsAString &result) { ALOG_BRIDGE("AndroidBridge::GetStaticIntField %s", fieldName); JNIEnv *env = GetJNIEnv(); if (!env) return false; AutoLocalJNIFrame jniFrame(env, 3); jclass cls = env->FindClass(className); if (!cls) return false; jfieldID field = env->GetStaticFieldID(cls, fieldName, "Ljava/lang/String;"); if (!field) return false; jstring jstr = (jstring) env->GetStaticObjectField(cls, field); if (!jstr) return false; result.Assign(nsJNIString(jstr)); return true; } void AndroidBridge::SetKeepScreenOn(bool on) { ALOG_BRIDGE("AndroidBridge::SetKeepScreenOn"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(sBridge->mGeckoAppShellClass, sBridge->jSetKeepScreenOn, on); } // Available for places elsewhere in the code to link to. bool mozilla_AndroidBridge_SetMainThread(void *thr) { return AndroidBridge::Bridge()->SetMainThread(thr); } jclass GetGeckoAppShellClass() { return mozilla::AndroidBridge::GetGeckoAppShellClass(); } void AndroidBridge::ScanMedia(const nsAString& aFile, const nsACString& aMimeType) { JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jstrFile = env->NewString(nsPromiseFlatString(aFile).get(), aFile.Length()); nsString mimeType2; CopyUTF8toUTF16(aMimeType, mimeType2); jstring jstrMimeTypes = env->NewString(nsPromiseFlatString(mimeType2).get(), mimeType2.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jScanMedia, jstrFile, jstrMimeTypes); } void AndroidBridge::CreateShortcut(const nsAString& aTitle, const nsAString& aURI, const nsAString& aIconData, const nsAString& aIntent) { JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jstrTitle = env->NewString(nsPromiseFlatString(aTitle).get(), aTitle.Length()); jstring jstrURI = env->NewString(nsPromiseFlatString(aURI).get(), aURI.Length()); jstring jstrIconData = env->NewString(nsPromiseFlatString(aIconData).get(), aIconData.Length()); jstring jstrIntent = env->NewString(nsPromiseFlatString(aIntent).get(), aIntent.Length()); if (!jstrURI || !jstrTitle || !jstrIconData) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jCreateShortcut, jstrTitle, jstrURI, jstrIconData, jstrIntent); } void AndroidBridge::PostToJavaThread(JNIEnv *env, nsIRunnable* aRunnable, bool aMainThread) { mRunnableQueue.AppendObject(aRunnable); AutoLocalJNIFrame jniFrame(env); env->CallStaticVoidMethod(mGeckoAppShellClass, jPostToJavaThread, (jboolean)aMainThread); } void AndroidBridge::ExecuteNextRunnable(JNIEnv *env) { if (mRunnableQueue.Count() > 0) { nsIRunnable* r = mRunnableQueue[0]; r->Run(); mRunnableQueue.RemoveObjectAt(0); } } void AndroidBridge::OpenGraphicsLibraries() { if (!mOpenedGraphicsLibraries) { // Try to dlopen libjnigraphics.so for direct bitmap access on // Android 2.2+ (API level 8) mOpenedGraphicsLibraries = true; mHasNativeWindowAccess = false; mHasNativeBitmapAccess = false; void *handle = dlopen("libjnigraphics.so", RTLD_LAZY | RTLD_LOCAL); if (handle) { AndroidBitmap_getInfo = (int (*)(JNIEnv *, jobject, void *))dlsym(handle, "AndroidBitmap_getInfo"); AndroidBitmap_lockPixels = (int (*)(JNIEnv *, jobject, void **))dlsym(handle, "AndroidBitmap_lockPixels"); AndroidBitmap_unlockPixels = (int (*)(JNIEnv *, jobject))dlsym(handle, "AndroidBitmap_unlockPixels"); mHasNativeBitmapAccess = AndroidBitmap_getInfo && AndroidBitmap_lockPixels && AndroidBitmap_unlockPixels; ALOG_BRIDGE("Successfully opened libjnigraphics.so, have native bitmap access? %d", mHasNativeBitmapAccess); } // Try to dlopen libandroid.so for and native window access on // Android 2.3+ (API level 9) handle = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL); if (handle) { ANativeWindow_fromSurface = (void* (*)(JNIEnv*, jobject))dlsym(handle, "ANativeWindow_fromSurface"); ANativeWindow_release = (void (*)(void*))dlsym(handle, "ANativeWindow_release"); ANativeWindow_setBuffersGeometry = (int (*)(void*, int, int, int)) dlsym(handle, "ANativeWindow_setBuffersGeometry"); ANativeWindow_lock = (int (*)(void*, void*, void*)) dlsym(handle, "ANativeWindow_lock"); ANativeWindow_unlockAndPost = (int (*)(void*))dlsym(handle, "ANativeWindow_unlockAndPost"); mHasNativeWindowAccess = ANativeWindow_fromSurface && ANativeWindow_release && ANativeWindow_lock && ANativeWindow_unlockAndPost; ALOG_BRIDGE("Successfully opened libandroid.so, have native window access? %d", mHasNativeWindowAccess); } } } void AndroidBridge::FireAndWaitForTracerEvent() { JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jFireAndWaitForTracerEvent); } namespace mozilla { class TracerRunnable : public nsRunnable{ public: TracerRunnable() { mTracerLock = new Mutex("TracerRunnable"); mTracerCondVar = new CondVar(*mTracerLock, "TracerRunnable"); mMainThread = do_GetMainThread(); } ~TracerRunnable() { delete mTracerCondVar; delete mTracerLock; mTracerLock = nsnull; mTracerCondVar = nsnull; } virtual nsresult Run() { MutexAutoLock lock(*mTracerLock); if (!AndroidBridge::Bridge()) return NS_OK; AndroidBridge::Bridge()->FireAndWaitForTracerEvent(); mHasRun = PR_TRUE; mTracerCondVar->Notify(); return NS_OK; } bool Fire() { if (!mTracerLock || !mTracerCondVar) return false; MutexAutoLock lock(*mTracerLock); mHasRun = PR_FALSE; mMainThread->Dispatch(this, NS_DISPATCH_NORMAL); while (!mHasRun) mTracerCondVar->Wait(); return true; } void Signal() { MutexAutoLock lock(*mTracerLock); mHasRun = PR_TRUE; mTracerCondVar->Notify(); } private: Mutex* mTracerLock; CondVar* mTracerCondVar; PRBool mHasRun; nsCOMPtr mMainThread; }; nsCOMPtr sTracerRunnable; bool InitWidgetTracing() { if (!sTracerRunnable) sTracerRunnable = new TracerRunnable(); return true; } void CleanUpWidgetTracing() { if (sTracerRunnable) delete sTracerRunnable; sTracerRunnable = nsnull; } bool FireAndWaitForTracerEvent() { if (sTracerRunnable) return sTracerRunnable->Fire(); return false; } void SignalTracerThread() { if (sTracerRunnable) return sTracerRunnable->Signal(); } } bool AndroidBridge::HasNativeBitmapAccess() { OpenGraphicsLibraries(); return mHasNativeBitmapAccess; } bool AndroidBridge::ValidateBitmap(jobject bitmap, int width, int height) { // This structure is defined in Android API level 8's // Because we can't depend on this, we get the function pointers via dlsym // and define this struct ourselves. struct BitmapInfo { uint32_t width; uint32_t height; uint32_t stride; uint32_t format; uint32_t flags; }; int err; struct BitmapInfo info = { 0, }; JNIEnv *env = GetJNIEnv(); if (!env) return false; if ((err = AndroidBitmap_getInfo(env, bitmap, &info)) != 0) { ALOG_BRIDGE("AndroidBitmap_getInfo failed! (error %d)", err); return false; } if (info.width != width || info.height != height) return false; return true; } bool AndroidBridge::InitCamera(const nsCString& contentType, PRUint32 camera, PRUint32 *width, PRUint32 *height, PRUint32 *fps) { JNIEnv *env = GetJNIEnv(); if (!env) return false; AutoLocalJNIFrame jniFrame(env); NS_ConvertASCIItoUTF16 s(contentType); jstring jstrContentType = env->NewString(s.get(), NS_strlen(s.get())); jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jInitCamera, jstrContentType, camera, *width, *height); jintArray arr = static_cast(obj); if (!arr) return false; jint *elements = env->GetIntArrayElements(arr, 0); *width = elements[1]; *height = elements[2]; *fps = elements[3]; bool res = elements[0] == 1; env->ReleaseIntArrayElements(arr, elements, 0); return res; } void AndroidBridge::CloseCamera() { JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); env->CallStaticVoidMethod(mGeckoAppShellClass, jCloseCamera); } void AndroidBridge::EnableBatteryNotifications() { ALOG_BRIDGE("AndroidBridge::EnableBatteryObserver"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jEnableBatteryNotifications); } void AndroidBridge::DisableBatteryNotifications() { ALOG_BRIDGE("AndroidBridge::DisableBatteryNotifications"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jDisableBatteryNotifications); } void AndroidBridge::GetCurrentBatteryInformation(hal::BatteryInformation* aBatteryInfo) { ALOG_BRIDGE("AndroidBridge::GetCurrentBatteryInformation"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); // 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. jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetCurrentBatteryInformation); jdoubleArray arr = static_cast(obj); if (!arr || env->GetArrayLength(arr) != 3) { return; } jdouble* info = env->GetDoubleArrayElements(arr, 0); aBatteryInfo->level() = info[0]; aBatteryInfo->charging() = info[1] == 1.0f; aBatteryInfo->remainingTime() = info[2]; env->ReleaseDoubleArrayElements(arr, info, 0); } void AndroidBridge::HandleGeckoMessage(const nsAString &aMessage, nsAString &aRet) { ALOG_BRIDGE("%s", __PRETTY_FUNCTION__); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env, 1); jstring jMessage = env->NewString(nsPromiseFlatString(aMessage).get(), aMessage.Length()); jstring returnMessage = static_cast(env->CallStaticObjectMethod(mGeckoAppShellClass, jHandleGeckoMessage, jMessage)); jthrowable ex = env->ExceptionOccurred(); if (ex) { env->ExceptionDescribe(); env->ExceptionClear(); } nsJNIString jniStr(returnMessage); aRet.Assign(jniStr); ALOG_BRIDGE("leaving %s", __PRETTY_FUNCTION__); } static nsCOMPtr gDrawMetadataProvider = NULL; nsCOMPtr AndroidBridge::GetDrawMetadataProvider() { return gDrawMetadataProvider; } void AndroidBridge::CheckURIVisited(const nsAString& aURI) { JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env, 1); jstring jstrURI = env->NewString(nsPromiseFlatString(aURI).get(), aURI.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jCheckUriVisited, jstrURI); } void AndroidBridge::MarkURIVisited(const nsAString& aURI) { JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env, 1); jstring jstrURI = env->NewString(nsPromiseFlatString(aURI).get(), aURI.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jMarkUriVisited, jstrURI); } void AndroidBridge::EmitGeckoAccessibilityEvent (PRInt32 eventType, const nsAString& role, const nsAString& text, const nsAString& description, bool enabled, bool checked, bool password) { JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jstrRole = env->NewString(nsPromiseFlatString(role).get(), role.Length()); jstring jstrText = env->NewString(nsPromiseFlatString(text).get(), text.Length()); jstring jstrDescription = env->NewString(nsPromiseFlatString(description).get(), description.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jEmitGeckoAccessibilityEvent, eventType, jstrRole, jstrText, jstrDescription, enabled, checked, password); } PRUint16 AndroidBridge::GetNumberOfMessagesForText(const nsAString& aText) { ALOG_BRIDGE("AndroidBridge::GetNumberOfMessagesForText"); JNIEnv *env = GetJNIEnv(); if (!env) return 0; AutoLocalJNIFrame jniFrame(env); jstring jText = env->NewString(PromiseFlatString(aText).get(), aText.Length()); return env->CallStaticIntMethod(mGeckoAppShellClass, jNumberOfMessages, jText); } void AndroidBridge::SendMessage(const nsAString& aNumber, const nsAString& aMessage, PRInt32 aRequestId, PRUint64 aProcessId) { ALOG_BRIDGE("AndroidBridge::SendMessage"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jstring jNumber = env->NewString(PromiseFlatString(aNumber).get(), aNumber.Length()); jstring jMessage = env->NewString(PromiseFlatString(aMessage).get(), aMessage.Length()); env->CallStaticVoidMethod(mGeckoAppShellClass, jSendMessage, jNumber, jMessage, aRequestId, aProcessId); } PRInt32 AndroidBridge::SaveSentMessage(const nsAString& aRecipient, const nsAString& aBody, PRUint64 aDate) { ALOG_BRIDGE("AndroidBridge::SaveSentMessage"); JNIEnv *env = GetJNIEnv(); if (!env) return 0; AutoLocalJNIFrame jniFrame(env, 1); jstring jRecipient = env->NewString(PromiseFlatString(aRecipient).get(), aRecipient.Length()); jstring jBody = env->NewString(PromiseFlatString(aBody).get(), aBody.Length()); return env->CallStaticIntMethod(mGeckoAppShellClass, jSaveSentMessage, jRecipient, jBody, aDate); } void AndroidBridge::GetMessage(PRInt32 aMessageId, PRInt32 aRequestId, PRUint64 aProcessId) { ALOG_BRIDGE("AndroidBridge::GetMessage"); JNIEnv *env = env; if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jGetMessage, aMessageId, aRequestId, aProcessId); } void AndroidBridge::DeleteMessage(PRInt32 aMessageId, PRInt32 aRequestId, PRUint64 aProcessId) { ALOG_BRIDGE("AndroidBridge::DeleteMessage"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jDeleteMessage, aMessageId, aRequestId, aProcessId); } void AndroidBridge::CreateMessageList(const dom::sms::SmsFilterData& aFilter, bool aReverse, PRInt32 aRequestId, PRUint64 aProcessId) { ALOG_BRIDGE("AndroidBridge::CreateMessageList"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); jobjectArray numbers = (jobjectArray)env->NewObjectArray(aFilter.numbers().Length(), env->FindClass("java/lang/String"), env->NewStringUTF("")); for (PRUint32 i = 0; i < aFilter.numbers().Length(); ++i) { env->SetObjectArrayElement(numbers, i, env->NewStringUTF(NS_ConvertUTF16toUTF8(aFilter.numbers()[i]).get())); } env->CallStaticVoidMethod(mGeckoAppShellClass, jCreateMessageList, aFilter.startDate(), aFilter.endDate(), numbers, aFilter.numbers().Length(), aFilter.delivery(), aReverse, aRequestId, aProcessId); } void AndroidBridge::GetNextMessageInList(PRInt32 aListId, PRInt32 aRequestId, PRUint64 aProcessId) { ALOG_BRIDGE("AndroidBridge::GetNextMessageInList"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jGetNextMessageinList, aListId, aRequestId, aProcessId); } void AndroidBridge::ClearMessageList(PRInt32 aListId) { ALOG_BRIDGE("AndroidBridge::ClearMessageList"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jClearMessageList, aListId); } void AndroidBridge::GetCurrentNetworkInformation(hal::NetworkInformation* aNetworkInfo) { ALOG_BRIDGE("AndroidBridge::GetCurrentNetworkInformation"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); // 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. jobject obj = env->CallStaticObjectMethod(mGeckoAppShellClass, jGetCurrentNetworkInformation); jdoubleArray arr = static_cast(obj); if (!arr || env->GetArrayLength(arr) != 2) { return; } jdouble* info = env->GetDoubleArrayElements(arr, 0); aNetworkInfo->bandwidth() = info[0]; aNetworkInfo->canBeMetered() = info[1] == 1.0f; env->ReleaseDoubleArrayElements(arr, info, 0); } void AndroidBridge::EnableNetworkNotifications() { ALOG_BRIDGE("AndroidBridge::EnableNetworkNotifications"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jEnableNetworkNotifications); } void AndroidBridge::DisableNetworkNotifications() { ALOG_BRIDGE("AndroidBridge::DisableNetworkNotifications"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jDisableNetworkNotifications); } void * AndroidBridge::LockBitmap(jobject bitmap) { int err; void *buf; JNIEnv *env = GetJNIEnv(); if (!env) return nsnull; if ((err = AndroidBitmap_lockPixels(env, bitmap, &buf)) != 0) { ALOG_BRIDGE("AndroidBitmap_lockPixels failed! (error %d)", err); buf = nsnull; } return buf; } void AndroidBridge::UnlockBitmap(jobject bitmap) { int err; JNIEnv *env = GetJNIEnv(); if (!env) return; if ((err = AndroidBitmap_unlockPixels(env, bitmap)) != 0) ALOG_BRIDGE("AndroidBitmap_unlockPixels failed! (error %d)", err); } bool AndroidBridge::HasNativeWindowAccess() { OpenGraphicsLibraries(); return mHasNativeWindowAccess; } void* AndroidBridge::AcquireNativeWindow(jobject surface) { if (!HasNativeWindowAccess()) return nsnull; JNIEnv *env = GetJNIEnv(); if (!env) return nsnull; return ANativeWindow_fromSurface(env, surface); } void AndroidBridge::ReleaseNativeWindow(void *window) { if (!window) return; ANativeWindow_release(window); } bool AndroidBridge::SetNativeWindowFormat(void *window, int width, int height, int format) { return ANativeWindow_setBuffersGeometry(window, width, height, format) == 0; } bool AndroidBridge::LockWindow(void *window, unsigned char **bits, int *width, int *height, int *format, int *stride) { /* Copied from native_window.h in Android NDK (platform-9) */ typedef struct ANativeWindow_Buffer { // The number of pixels that are show horizontally. int32_t width; // The number of pixels that are shown vertically. int32_t height; // The number of *pixels* that a line in the buffer takes in // memory. This may be >= width. int32_t stride; // The format of the buffer. One of WINDOW_FORMAT_* int32_t format; // The actual bits. void* bits; // Do not touch. uint32_t reserved[6]; } ANativeWindow_Buffer; int err; ANativeWindow_Buffer buffer; *bits = NULL; *width = *height = *format = 0; if ((err = ANativeWindow_lock(window, (void*)&buffer, NULL)) != 0) { ALOG_BRIDGE("ANativeWindow_lock failed! (error %d)", err); return false; } *bits = (unsigned char*)buffer.bits; *width = buffer.width; *height = buffer.height; *format = buffer.format; *stride = buffer.stride; return true; } bool AndroidBridge::UnlockWindow(void* window) { int err; if ((err = ANativeWindow_unlockAndPost(window)) != 0) { ALOG_BRIDGE("ANativeWindow_unlockAndPost failed! (error %d)", err); return false; } return true; } bool AndroidBridge::IsTablet() { JNIEnv *env = GetJNIEnv(); if (!env) return false; return env->CallStaticBooleanMethod(mGeckoAppShellClass, jIsTablet); } /* Implementation file */ NS_IMPL_ISUPPORTS1(nsAndroidBridge, nsIAndroidBridge) nsAndroidBridge::nsAndroidBridge() { } nsAndroidBridge::~nsAndroidBridge() { } /* void handleGeckoEvent (in AString message); */ NS_IMETHODIMP nsAndroidBridge::HandleGeckoMessage(const nsAString & message, nsAString &aRet NS_OUTPARAM) { AndroidBridge::Bridge()->HandleGeckoMessage(message, aRet); return NS_OK; } /* void SetDrawMetadataProvider (in nsIAndroidDrawMetadataProvider message); */ NS_IMETHODIMP nsAndroidBridge::SetDrawMetadataProvider(nsIAndroidDrawMetadataProvider *aProvider) { gDrawMetadataProvider = aProvider; return NS_OK; } void AndroidBridge::SetPreventPanning(bool aPreventPanning) { ALOG_BRIDGE("AndroidBridge::PreventPanning"); JNIEnv *env = GetJNIEnv(); if (!env) return; env->CallStaticVoidMethod(mGeckoAppShellClass, jSetPreventPanning, (jboolean)aPreventPanning); } // DO NOT USE THIS unless you need to access JNI from // non-main threads. This is probably not what you want. // Questions, ask blassey or dougt. static void JavaThreadDetachFunc(void *arg) { JNIEnv *env = (JNIEnv*) arg; JavaVM *vm = NULL; env->GetJavaVM(&vm); vm->DetachCurrentThread(); } extern "C" { __attribute__ ((visibility("default"))) JNIEnv * GetJNIForThread() { JNIEnv *jEnv = NULL; JavaVM *jVm = mozilla::AndroidBridge::GetVM(); if (!jVm) { __android_log_print(ANDROID_LOG_INFO, "GetJNIForThread", "Returned a null VM"); return NULL; } int status = jVm->GetEnv((void**) &jEnv, JNI_VERSION_1_2); if (status < 0) { status = jVm->AttachCurrentThread(&jEnv, NULL); if (status < 0) { __android_log_print(ANDROID_LOG_INFO, "GetJNIForThread", "Could not attach"); return NULL; } static PRUintn sJavaEnvThreadIndex = 0; PR_NewThreadPrivateIndex(&sJavaEnvThreadIndex, JavaThreadDetachFunc); PR_SetThreadPrivate(sJavaEnvThreadIndex, jEnv); } if (!jEnv) { __android_log_print(ANDROID_LOG_INFO, "GetJNIForThread", "returning NULL"); } return jEnv; } } jobject AndroidBridge::CreateSurface() { #ifndef MOZ_JAVA_COMPOSITOR return NULL; #else AutoLocalJNIFrame frame(1); JNIEnv* env = GetJNIForThread(); jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell"); jmethodID method = env->GetStaticMethodID(cls, "createSurface", "()Landroid/view/Surface;"); jobject surface = env->CallStaticObjectMethod(cls, method); if (surface) env->NewGlobalRef(surface); return surface; #endif } void AndroidBridge::DestroySurface(jobject surface) { #ifdef MOZ_JAVA_COMPOSITOR AutoLocalJNIFrame frame(1); JNIEnv* env = GetJNIForThread(); jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell"); jmethodID method = env->GetStaticMethodID(cls, "destroySurface", "(Landroid/view/Surface;)V"); env->CallStaticVoidMethod(cls, method, surface); env->DeleteGlobalRef(surface); #endif } void AndroidBridge::ShowSurface(jobject surface, const gfxRect& aRect, bool aInverted, bool aBlend) { #ifdef MOZ_JAVA_COMPOSITOR AutoLocalJNIFrame frame; JNIEnv* env = GetJNIForThread(); jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell"); nsAutoString metadata; nsCOMPtr metadataProvider = GetDrawMetadataProvider(); metadataProvider->GetDrawMetadata(metadata); jstring jMetadata = env->NewString(nsPromiseFlatString(metadata).get(), metadata.Length()); jmethodID method = env->GetStaticMethodID(cls, "showSurface", "(Landroid/view/Surface;IIIIZZLjava/lang/String;)V"); env->CallStaticVoidMethod(cls, method, surface, (int)aRect.x, (int)aRect.y, (int)aRect.width, (int)aRect.height, aInverted, aBlend, jMetadata); #endif } void AndroidBridge::HideSurface(jobject surface) { #ifdef MOZ_JAVA_COMPOSITOR AutoLocalJNIFrame frame(1); JNIEnv* env = GetJNIForThread(); jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell"); jmethodID method = env->GetStaticMethodID(cls, "hideSurface", "(Landroid/view/Surface;)V"); env->CallStaticVoidMethod(cls, method, surface); #endif } /* void takeScreenshot (in nsIDOMWindow win, in PRInt32 srcX, in PRInt32 srcY, in PRInt32 srcW, in PRInt32 srcH, in PRInt32 dstX, in PRInt32 dstY, in PRInt32 dstW, in PRInt32 dstH, in AString color); */ NS_IMETHODIMP nsAndroidBridge::TakeScreenshot(nsIDOMWindow *window, PRInt32 srcX, PRInt32 srcY, PRInt32 srcW, PRInt32 srcH, PRInt32 dstW, PRInt32 dstH, PRInt32 tabId) { nsCOMPtr win = do_QueryInterface(window); if (!win) return NS_ERROR_FAILURE; nsRefPtr presContext; nsIDocShell* docshell = win->GetDocShell(); if (docshell) { docshell->GetPresContext(getter_AddRefs(presContext)); } if (!presContext) return NS_ERROR_FAILURE; nscolor bgColor = NS_RGB(255, 255, 255); nsIPresShell* presShell = presContext->PresShell(); PRUint32 renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | nsIPresShell::RENDER_DOCUMENT_RELATIVE); nsRect r(nsPresContext::CSSPixelsToAppUnits(srcX), nsPresContext::CSSPixelsToAppUnits(srcY), nsPresContext::CSSPixelsToAppUnits(srcW), nsPresContext::CSSPixelsToAppUnits(srcH)); nsRefPtr surf = new gfxImageSurface(nsIntSize(dstW, dstH), gfxASurface::ImageFormatRGB16_565); nsRefPtr context = new gfxContext(surf); nsresult rv = presShell->RenderDocument(r, renderDocFlags, bgColor, context); NS_ENSURE_SUCCESS(rv, rv); AndroidBridge::Bridge()->NotifyScreenshot(surf->Data(), surf->GetDataSize(), tabId, dstW, dstH); return NS_OK; } void AndroidBridge::NotifyScreenshot(unsigned char* data, int size, int tabId, int width, int height) { JNIEnv* jenv = GetJNIEnv(); if (!jenv) return; AutoLocalJNIFrame jniFrame(jenv, 1); jobject buffer = jenv->NewDirectByteBuffer(data, size); if (!buffer) return; jenv->CallStaticVoidMethod(mGeckoAppShellClass, jNotifyScreenShot, buffer, tabId, width, height); }