tdf#129833 android: Allow editing writable docs passed by Intent

When a document is passed by an Intent in Android
Viewer, allow editing if the Intent has flag
'Intent.FLAG_GRANT_WRITE_URI_PERMISSION' set.

Since LibreOffice operates on a temporary copy
in this case, write the content of the
temporary file back to the original URI
when saving.

This in particular allows editing documents
passed from third-party apps, e.g. opened from
Android's "Files" app or the NextCloud app.

The online-based Android app already does
something similar.

Change-Id: Icf252a95dd9a8089ca8610ccf3edfbeee2682e1a
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/112767
Tested-by: Jenkins
Reviewed-by: Michael Weghorn <m.weghorn@posteo.de>
diff --git a/android/source/res/values-de/strings.xml b/android/source/res/values-de/strings.xml
index ff58b14..64eaa84 100644
--- a/android/source/res/values-de/strings.xml
+++ b/android/source/res/values-de/strings.xml
@@ -96,6 +96,7 @@
    <string name="message_saved">Speichern beendet</string>
    <string name="message_saving">Dokument wird gespeichert…</string>
    <string name="message_save_incomplete">Speichern unvollständig. Gab es Änderungen?</string>
    <string name="message_saving_failed">Speichern des Dokuments ist fehlgeschlagen.</string>
    <string name="create_new_file_success">"Neue Datei angelegt - "</string>
    <string name="create_new_file_error">Neue Datei konnte nicht angelegt weden, bitte Prüfen Sie den eingegeben Dateinamen.</string>

diff --git a/android/source/res/values/strings.xml b/android/source/res/values/strings.xml
index aa32497..a44d81e 100644
--- a/android/source/res/values/strings.xml
+++ b/android/source/res/values/strings.xml
@@ -95,6 +95,7 @@
    <!-- Feedback messages -->
    <string name="message_saved">Save complete</string>
    <string name="message_saving">Saving the document…</string>
    <string name="message_saving_failed">Saving the document failed.</string>
    <string name="message_save_incomplete">Save incomplete. Were there any changes?</string>
    <string name="create_new_file_success">"Created new file - "</string>
    <string name="create_new_file_error">Unable to create new file, please check entered file name.</string>
diff --git a/android/source/src/java/org/libreoffice/InvalidationHandler.java b/android/source/src/java/org/libreoffice/InvalidationHandler.java
index 32e9b56..588fec9 100644
--- a/android/source/src/java/org/libreoffice/InvalidationHandler.java
+++ b/android/source/src/java/org/libreoffice/InvalidationHandler.java
@@ -139,7 +139,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes
            JSONObject payloadObject = new JSONObject(payload);
            if (payloadObject.getString("commandName").equals(".uno:Save")) {
                if (payloadObject.getString("success").equals("true")) {
                    mContext.saveFilesToCloud();
                    mContext.saveFileToOriginalSource();
                }
            }else if(payloadObject.getString("commandName").equals(".uno:Name") ||
                    payloadObject.getString("commandName").equals(".uno:RenamePage")){
diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
index b640fa4..bf6108e 100644
--- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -46,9 +46,11 @@ import org.mozilla.gecko.gfx.GeckoLayerClient;
import org.mozilla.gecko.gfx.LayerView;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
@@ -191,7 +193,8 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
            if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
                if (copyFileToTemp() && mTempFile != null) {
                    mInputFile = mTempFile;
                    mbISReadOnlyMode = true;
                    boolean isReadOnlyDoc = (getIntent().getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
                    mbISReadOnlyMode = !isExperimentalMode()  || isReadOnlyDoc;
                    Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + getIntent().getData().getPath());

                    String displayName = extractDisplayNameFromIntent();
@@ -355,6 +358,61 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
        LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:Save", true));
    }

    public void saveFileToOriginalSource() {
        if (documentUri != null) {
            // case where file was opened using IDocumentProvider from within LO app
            saveFilesToCloud();
        } else {
            // case where file was passed via Intent
            if (isReadOnlyMode() || mInputFile == null || getIntent().getData() == null || !getIntent().getData().getScheme().equals(ContentResolver.SCHEME_CONTENT))
                return;

            Uri uri = getIntent().getData();
            FileInputStream inputStream = null;
            OutputStream outputStream = null;

            try {
                inputStream = new FileInputStream(mInputFile);
                // OutputStream for the actual (original) location
                outputStream = getContentResolver().openOutputStream(uri);

                byte[] buffer = new byte[4096];
                int readBytes = inputStream.read(buffer);
                while (readBytes != -1) {
                    outputStream.write(buffer, 0, readBytes);
                    readBytes = inputStream.read(buffer);
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saved,
                            Toast.LENGTH_SHORT).show();
                    }
                });
                setDocumentChanged(false);
            } catch (Exception e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saving_failed,
                            Toast.LENGTH_SHORT).show();
                    }
                });
                e.printStackTrace();
            } finally {
                try {
                    if (inputStream != null)
                        inputStream.close();
                    if (outputStream != null)
                        outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void saveFilesToCloud(){
        final Activity activity = LibreOfficeMainActivity.this;
        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {