android: Call the SAVE directly from the native code.

Until now, for the "local save has completed, upload it back to the
content: URI" messages we were relying on the "local save" -> JavaScript
-> Java -> "upload to content:/ URI" chain.

It turns out though, that the WebView can be dead by the time we need
the notification that the save has completed.  This was particularly
seen on ChromeOS when the document was closed using the [x] in the
window decoration.

As a solution, we need to pass the info that the "local save" has
completed directly to Java.  So far this uses the same semantics as the
postMobileMessage() and reuse its code; but maybe in the future we'll
need to split this.

Change-Id: If1b93e4f76cee3abc6aebfc3e9072810ab73bb42
Reviewed-on: https://gerrit.libreoffice.org/c/online/+/98773
Tested-by: Jenkins
Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoffice@gmail.com>
Reviewed-by: Jan Holesovsky <kendy@collabora.com>
This commit is contained in:
Jan Holesovsky 2020-07-14 14:27:03 +02:00
parent cb68187c5e
commit 1b3732b941
4 changed files with 51 additions and 22 deletions

View file

@ -36,6 +36,10 @@ static int closeNotificationPipeForForwardingThread[2] = {-1, -1};
static JavaVM* javaVM = nullptr;
static bool lokInitialized = false;
// Remember the reference to the LOActivity
jclass g_loActivityClz = nullptr;
jobject g_loActivityObj = nullptr;
extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void*) {
javaVM = vm;
@ -90,7 +94,7 @@ public:
JNIEnv *getEnv() const { return _env; }
};
static void send2JS(const JNIThreadContext &jctx, jclass loActivityClz, jobject loActivityObj, const std::vector<char>& buffer)
static void send2JS(const JNIThreadContext &jctx, const std::vector<char>& buffer)
{
LOG_DBG("Send to JS: " << LOOLProtocol::getAbbreviatedMessage(buffer.data(), buffer.size()));
@ -150,8 +154,22 @@ static void send2JS(const JNIThreadContext &jctx, jclass loActivityClz, jobject
JNIEnv *env = jctx.getEnv();
jstring jstr = env->NewStringUTF(js.c_str());
jmethodID callFakeWebsocket = env->GetMethodID(loActivityClz, "callFakeWebsocketOnMessage", "(Ljava/lang/String;)V");
env->CallVoidMethod(loActivityObj, callFakeWebsocket, jstr);
jmethodID callFakeWebsocket = env->GetMethodID(g_loActivityClz, "callFakeWebsocketOnMessage", "(Ljava/lang/String;)V");
env->CallVoidMethod(g_loActivityObj, callFakeWebsocket, jstr);
env->DeleteLocalRef(jstr);
if (env->ExceptionCheck())
env->ExceptionDescribe();
}
void postDirectMessage(std::string message)
{
JNIThreadContext ctx;
JNIEnv *env = ctx.getEnv();
jstring jstr = env->NewStringUTF(message.c_str());
jmethodID callPostMobileMessage = env->GetMethodID(g_loActivityClz, "postMobileMessage", "(Ljava/lang/String;)V");
env->CallVoidMethod(g_loActivityObj, callPostMobileMessage, jstr);
env->DeleteLocalRef(jstr);
if (env->ExceptionCheck())
@ -171,7 +189,7 @@ void closeDocument()
/// Handle a message from JavaScript.
extern "C" JNIEXPORT void JNICALL
Java_org_libreoffice_androidlib_LOActivity_postMobileMessageNative(JNIEnv *env, jobject instance, jstring message)
Java_org_libreoffice_androidlib_LOActivity_postMobileMessageNative(JNIEnv *env, jobject, jstring message)
{
const char *string_value = env->GetStringUTFChars(message, nullptr);
@ -198,11 +216,7 @@ Java_org_libreoffice_androidlib_LOActivity_postMobileMessageNative(JNIEnv *env,
fakeSocketPipe2(closeNotificationPipeForForwardingThread);
// Start another thread to read responses and forward them to the JavaScript
jclass clz = env->GetObjectClass(instance);
jclass loActivityClz = (jclass) env->NewGlobalRef(clz);
jobject loActivityObj = env->NewGlobalRef(instance);
std::thread([loActivityClz, loActivityObj, currentFakeClientFd]
std::thread([currentFakeClientFd]
{
Util::setThreadName("app2js");
JNIThreadContext ctx;
@ -239,7 +253,7 @@ Java_org_libreoffice_androidlib_LOActivity_postMobileMessageNative(JNIEnv *env,
return;
std::vector<char> buf(n);
n = fakeSocketRead(currentFakeClientFd, buf.data(), n);
send2JS(ctx, loActivityClz, loActivityObj, buf);
send2JS(ctx, buf);
}
}
else
@ -287,10 +301,18 @@ extern "C" jboolean libreofficekit_initialize(JNIEnv* env, jstring dataDir, jstr
/// Create the LOOLWSD instance.
extern "C" JNIEXPORT void JNICALL
Java_org_libreoffice_androidlib_LOActivity_createLOOLWSD(JNIEnv *env, jobject, jstring dataDir, jstring cacheDir, jstring apkFile, jobject assetManager, jstring loadFileURL)
Java_org_libreoffice_androidlib_LOActivity_createLOOLWSD(JNIEnv *env, jobject instance, jstring dataDir, jstring cacheDir, jstring apkFile, jobject assetManager, jstring loadFileURL)
{
fileURL = std::string(env->GetStringUTFChars(loadFileURL, nullptr));
// remember the LOActivity class and object to be able to call back
env->DeleteGlobalRef(g_loActivityClz);
env->DeleteGlobalRef(g_loActivityObj);
jclass clz = env->GetObjectClass(instance);
g_loActivityClz = (jclass) env->NewGlobalRef(clz);
g_loActivityObj = env->NewGlobalRef(instance);
// already initialized?
if (lokInitialized)
{
@ -332,7 +354,7 @@ Java_org_libreoffice_androidlib_LOActivity_createLOOLWSD(JNIEnv *env, jobject, j
extern "C"
JNIEXPORT void JNICALL
Java_org_libreoffice_androidlib_LOActivity_saveAs(JNIEnv *env, jobject instance,
Java_org_libreoffice_androidlib_LOActivity_saveAs(JNIEnv *env, jobject,
jstring fileUri_, jstring format_) {
const char *fileUri = env->GetStringUTFChars(fileUri_, 0);
const char *format = env->GetStringUTFChars(format_, 0);
@ -345,7 +367,7 @@ Java_org_libreoffice_androidlib_LOActivity_saveAs(JNIEnv *env, jobject instance,
extern "C"
JNIEXPORT void JNICALL
Java_org_libreoffice_androidlib_LOActivity_postUnoCommand(JNIEnv* pEnv, jobject instance,
Java_org_libreoffice_androidlib_LOActivity_postUnoCommand(JNIEnv* pEnv, jobject,
jstring command, jstring arguments, jboolean bNotifyWhenFinished)
{
const char* pCommand = pEnv->GetStringUTFChars(command, nullptr);
@ -379,7 +401,7 @@ const char* copyJavaString(JNIEnv* pEnv, jstring aJavaString)
extern "C"
JNIEXPORT jboolean JNICALL
Java_org_libreoffice_androidlib_LOActivity_getClipboardContent(JNIEnv *env, jobject instance, jobject lokClipboardData)
Java_org_libreoffice_androidlib_LOActivity_getClipboardContent(JNIEnv *env, jobject, jobject lokClipboardData)
{
const char** mimeTypes = nullptr;
size_t outCount = 0;
@ -478,7 +500,7 @@ Java_org_libreoffice_androidlib_LOActivity_getClipboardContent(JNIEnv *env, jobj
extern "C"
JNIEXPORT void JNICALL
Java_org_libreoffice_androidlib_LOActivity_setClipboardContent(JNIEnv *env, jobject instance, jobject lokClipboardData) {
Java_org_libreoffice_androidlib_LOActivity_setClipboardContent(JNIEnv *env, jobject, jobject lokClipboardData) {
jclass class_ArrayList= env->FindClass("java/util/ArrayList");
jmethodID methodId_ArrayList_ToArray = env->GetMethodID(class_ArrayList, "toArray", "()[Ljava/lang/Object;");
@ -524,7 +546,7 @@ Java_org_libreoffice_androidlib_LOActivity_setClipboardContent(JNIEnv *env, jobj
extern "C"
JNIEXPORT void JNICALL
Java_org_libreoffice_androidlib_LOActivity_paste(JNIEnv *env, jobject instance, jstring inMimeType, jbyteArray inData) {
Java_org_libreoffice_androidlib_LOActivity_paste(JNIEnv *env, jobject, jstring inMimeType, jbyteArray inData) {
const char* mimeType = env->GetStringUTFChars(inMimeType, nullptr);
size_t dataArrayLength = env->GetArrayLength(inData);

View file

@ -9,4 +9,7 @@
extern int loolwsd_server_socket_fd;
/** Equivalent of postMobileMessage(), but called directly from the native code. */
void postDirectMessage(std::string message);
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */

View file

@ -33,6 +33,10 @@
#include <Poco/Net/AcceptCertificateHandler.h>
#endif
#ifdef __ANDROID__
#include <androidapp.hpp>
#endif
#include <common/FileUtil.hpp>
#include <common/JsonUtil.hpp>
#include <common/Authorization.hpp>
@ -2469,7 +2473,7 @@ void ChildSession::loKitCallback(const int type, const std::string& payload)
break;
case LOK_CALLBACK_UNO_COMMAND_RESULT:
sendTextFrame("unocommandresult: " + payload);
#ifdef IOS
#if MOBILEAPP
{
// After the document has been saved (into the temporary copy that we set up in
// -[CODocument loadFromContents:ofType:error:]), save it also using the system API so
@ -2484,12 +2488,16 @@ void ChildSession::loKitCallback(const int type, const std::string& payload)
if (!commandName.isEmpty() && commandName.toString() == ".uno:Save" && !success.isEmpty() && success.toString() == "true")
{
#if defined(IOS)
CODocument *document = getDocumentDataForMobileAppDocId(_docManager->getMobileAppDocId()).coDocument;
[document saveToURL:[document fileURL]
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success) {
LOG_TRC("ChildSession::loKitCallback() save completion handler gets " << (success?"YES":"NO"));
}];
#elif defined(__ANDROID__)
postDirectMessage("SAVE " + payload);
#endif
}
}
#endif

View file

@ -938,11 +938,7 @@ function onCommandResult(e) {
postMessageObj['result'] = e.result && e.result.value;
}
if (window.ThisIsTheAndroidApp) {
window.postMobileMessage('SAVE ' + JSON.stringify(postMessageObj));
} else {
map.fire('postMessage', {msgId: 'Action_Save_Resp', args: postMessageObj});
}
map.fire('postMessage', {msgId: 'Action_Save_Resp', args: postMessageObj});
}
else if ((commandName === '.uno:Undo' || commandName === '.uno:Redo') &&
e.success === true && e.result.value && !isNaN(e.result.value)) { /*UNDO_CONFLICT*/