[Libreoffice-commits] core.git: Branch 'distro/lhm/libreoffice-7-1+backports' - 9 commits - android/source

Michael Weghorn (via logerrit) logerrit at kemper.freedesktop.org
Fri Apr 16 08:20:12 UTC 2021


 android/source/res/menu/main.xml                                      |   15 
 android/source/res/values-de/strings.xml                              |    3 
 android/source/res/values-tr/strings.xml                              |    1 
 android/source/res/values/strings.xml                                 |    3 
 android/source/src/java/org/libreoffice/FontController.java           |    2 
 android/source/src/java/org/libreoffice/InvalidationHandler.java      |    4 
 android/source/src/java/org/libreoffice/LOKitShell.java               |    4 
 android/source/src/java/org/libreoffice/LOKitTileProvider.java        |   89 +-
 android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java  |  298 +++++++---
 android/source/src/java/org/libreoffice/TileProvider.java             |   10 
 android/source/src/java/org/libreoffice/ToolbarController.java        |   24 
 android/source/src/java/org/libreoffice/ui/FileUtilities.java         |   42 -
 android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java |   51 -
 13 files changed, 325 insertions(+), 221 deletions(-)

New commits:
commit 10d1390980f986afef25f715ce41f02b3c586bf1
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 16:50:16 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:17:51 2021 +0200

    tdf#95615 android: Don't offer "Save" after opening template
    
    When the input document in Android Viewer is a template,
    a new doc is created and a plain '.uno:Save'
    (which 'LibreOfficeMainActivity#saveDocument' triggers when the
    "Save" menu entry is selected) will therefore fail.
    A proper URI to save to (rather than overwriting the
    template itself) is only known after a "Save As" anyway,
    so don't set the 'mDocument' member until then, which leads to
    the "Save" menu entry becoming disabled, just as is the
    case when explicitly choosing to create a new document in
    the start activity.
    
    For now, the check whether the document is a template
    checks whether the MIME type detected for the URI
    ends with "template", which is the case for ODF and
    OOXML types (like
    "application/vnd.oasis.opendocument.text-template" or
    "application/vnd.openxmlformats-officedocument.wordprocessingml.template").
    This can be refined further as needed, e.g. by explicitly
    adding more MIME types to check.
    
    (Editing the actual template instead of creating a new doc
    from it would be a different use case that remains
    unsupported also with this change in place.)
    
    Change-Id: I81ff957de27f620a026dbc01097b8061886293a1
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114157
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit 01521db61eb41447113c4bb671ac828a583c0cd1)

diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
index 066c05dc9662..f7f5f6ae0ed6 100644
--- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -82,7 +82,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
     private List<DocumentPartView> mDocumentPartView = new ArrayList<DocumentPartView>();
     private DocumentPartViewListAdapter mDocumentPartViewListAdapter;
     private DocumentOverlay mDocumentOverlay;
-    /** URI of the actual document. */
+    /** URI to save the document to. */
     private Uri mDocumentUri;
     /** Temporary local copy of the document. */
     private File mTempFile = null;
@@ -170,29 +170,38 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
 
         mbISReadOnlyMode = !isExperimentalMode();
 
-        mDocumentUri = getIntent().getData();
-        if (mDocumentUri != null) {
-            if (mDocumentUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
-                    || mDocumentUri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) {
+        final Uri docUri = getIntent().getData();
+        if (docUri != null) {
+            if (docUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
+                    || docUri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) {
                 final boolean isReadOnlyDoc  = (getIntent().getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
                 mbISReadOnlyMode = !isExperimentalMode() || isReadOnlyDoc;
-                Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + mDocumentUri.getPath());
+                Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + docUri.getPath());
 
-                String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), mDocumentUri);
+                String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri);
                 toolbarTop.setTitle(displayName);
 
-            } else if (mDocumentUri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
+            } else if (docUri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
                 mbISReadOnlyMode = true;
-                Log.d(LOGTAG, "SCHEME_FILE: getPath(): " + mDocumentUri.getPath());
-                toolbarTop.setTitle(mDocumentUri.getLastPathSegment());
+                Log.d(LOGTAG, "SCHEME_FILE: getPath(): " + docUri.getPath());
+                toolbarTop.setTitle(docUri.getLastPathSegment());
             }
             // create a temporary local copy to work with
-            boolean copyOK = copyFileToTemp() && mTempFile != null;
+            boolean copyOK = copyFileToTemp(docUri) && mTempFile != null;
             if (!copyOK) {
                 // TODO: can't open the file
-                Log.e(LOGTAG, "couldn't create temporary file from " + mDocumentUri);
+                Log.e(LOGTAG, "couldn't create temporary file from " + docUri);
                 return;
             }
+
+            // if input doc is a template, a new doc is created and a proper URI to save to
+            // will only be available after a "Save As"
+            if (isTemplate(docUri)) {
+                toolbarTop.setTitle(R.string.default_document_name);
+            } else {
+                mDocumentUri = docUri;
+            }
+
             LOKitShell.sendLoadEvent(mTempFile.getPath());
         } else if (getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY) != null) {
             // New document type string is not null, meaning we want to open a new document
@@ -275,7 +284,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         return mDocumentOverlay.getCurrentCursorPosition();
     }
 
-    private boolean copyFileToTemp() {
+    private boolean copyFileToTemp(Uri documentUri) {
         // CSV files need a .csv suffix to be opened in Calc.
         String suffix = null;
         String intentType = getIntent().getType();
@@ -286,7 +295,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         try {
             mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir());
             final FileOutputStream outputStream = new FileOutputStream(mTempFile);
-            return copyUriToStream(mDocumentUri, outputStream);
+            return copyUriToStream(documentUri, outputStream);
         } catch (FileNotFoundException e) {
             return false;
         } catch (IOException e) {
@@ -395,6 +404,14 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         }
     }
 
+    /**
+     * Returns whether the MIME type for the URI is considered one for a document template.
+     */
+    private boolean isTemplate(final Uri documentUri) {
+        final String mimeType = getContentResolver().getType(documentUri);
+        return FileUtilities.isTemplateMimeType(mimeType);
+    }
+
     public void saveFileToOriginalSource() {
         if (isReadOnlyMode() || mTempFile == null || mDocumentUri == null || !mDocumentUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))
             return;
diff --git a/android/source/src/java/org/libreoffice/ui/FileUtilities.java b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
index 0d51dd55e1e5..aed671205bef 100644
--- a/android/source/src/java/org/libreoffice/ui/FileUtilities.java
+++ b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
@@ -156,6 +156,14 @@ public class FileUtilities {
         return isHidden(file) && file.getName().endsWith(".png");
     }
 
+    /**
+     * Returns whether the passed MIME type is one for a document template.
+     */
+    public static boolean isTemplateMimeType(final String mimeType) {
+        // this works for ODF and OOXML template MIME types
+        return mimeType.endsWith("template");
+    }
+
     /**
      * Tries to retrieve the display (which should be the document name)
      * for the given URI using the given resolver.
commit b26b05746f8d9ce27347d01e601b61a2a97bbbe7
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 15:14:51 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:17:41 2021 +0200

    android: Show file picker for new docs on "Save As"
    
    When creating a new document in Android Viewer,
    don't show a file picker to choose where to save
    the file at once, but create a new document and
    only ask where to save the file once "Save As"
    has been selected.
    The plain "Save" entry is disabled until then,
    since no URI to save to is known at this time.
    
    The handling for the temporary local file,
    which is used for all of the "actual work"
    remains unchanged.
    
    While at it, rename
    'ToolbarController#disableMenuItem' to
    'ToolbarController#enableMenuItem' and
    invert the meaning of the boolean parameter
    since I find that more straightforward and it also
    better matches the naming of the underlying
    'MenuItem' method as well.
    
    Change-Id: I3eefada5531bfb2202aca25f2c9f170a463f87f6
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114152
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit 5ce43b2b4e79c51f0d8922caf77fa6492c05c2a7)

diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
index 1812ad253744..066c05dc9662 100644
--- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -169,24 +169,12 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         mDocumentOverlay = new DocumentOverlay(this, layerView);
 
         mbISReadOnlyMode = !isExperimentalMode();
-        boolean isNewDocument = false;
 
         mDocumentUri = getIntent().getData();
         if (mDocumentUri != null) {
             if (mDocumentUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
                     || mDocumentUri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) {
-                final boolean isReadOnlyDoc;
-                if (getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY) != null) {
-                    // New document type string is not null, meaning we want to open a new document
-                    String newDocumentType = getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY);
-                    // create a temporary local file, will be copied to the actual URI when saving
-                    loadNewDocument(newDocumentType);
-                    isNewDocument = true;
-                    isReadOnlyDoc = false;
-                } else {
-                    isReadOnlyDoc = (getIntent().getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
-                }
-
+                final boolean isReadOnlyDoc  = (getIntent().getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
                 mbISReadOnlyMode = !isExperimentalMode() || isReadOnlyDoc;
                 Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + mDocumentUri.getPath());
 
@@ -198,12 +186,6 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
                 Log.d(LOGTAG, "SCHEME_FILE: getPath(): " + mDocumentUri.getPath());
                 toolbarTop.setTitle(mDocumentUri.getLastPathSegment());
             }
-        } else {
-            Log.e(LOGTAG, "No document specified. This should never happen.");
-            return;
-        }
-
-        if (!isNewDocument) {
             // create a temporary local copy to work with
             boolean copyOK = copyFileToTemp() && mTempFile != null;
             if (!copyOK) {
@@ -212,6 +194,15 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
                 return;
             }
             LOKitShell.sendLoadEvent(mTempFile.getPath());
+        } else if (getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY) != null) {
+            // New document type string is not null, meaning we want to open a new document
+            String newDocumentType = getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY);
+            // create a temporary local file, will be copied to the actual URI when saving
+            loadNewDocument(newDocumentType);
+            toolbarTop.setTitle(getString(R.string.default_document_name));
+        } else {
+            Log.e(LOGTAG, "No document specified. This should never happen.");
+            return;
         }
 
         mDrawerLayout = findViewById(R.id.drawer_layout);
@@ -339,6 +330,8 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
 
         String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), mDocumentUri);
         toolbarTop.setTitle(displayName);
+        // make sure that "Save" menu item is enabled
+        getToolbarController().setupToolbars();
     }
 
     public void exportToPDF() {
@@ -880,6 +873,10 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         return mbISReadOnlyMode;
     }
 
+    public boolean hasLocationForSave() {
+        return mDocumentUri != null;
+    }
+
     public static void setDocumentChanged (boolean changed) {
         isDocumentChanged = changed;
     }
diff --git a/android/source/src/java/org/libreoffice/ToolbarController.java b/android/source/src/java/org/libreoffice/ToolbarController.java
index 1384339f8c30..d4985aee180e 100644
--- a/android/source/src/java/org/libreoffice/ToolbarController.java
+++ b/android/source/src/java/org/libreoffice/ToolbarController.java
@@ -45,12 +45,12 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener {
         clipboardManager = (ClipboardManager)mContext.getSystemService(Context.CLIPBOARD_SERVICE);
     }
 
-    public void disableMenuItem(final int menuItemId, final boolean disabled) {
+    private void enableMenuItem(final int menuItemId, final boolean enabled) {
         LOKitShell.getMainHandler().post(new Runnable() {
             public void run() {
                 MenuItem menuItem = mMainMenu.findItem(menuItemId);
                 if (menuItem != null) {
-                    menuItem.setEnabled(!disabled);
+                    menuItem.setEnabled(enabled);
                 } else {
                     Log.e(LOGTAG, "MenuItem not found.");
                 }
@@ -245,11 +245,14 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener {
     }
 
     void setupToolbars() {
-        // show message in case experimental mode is enabled (i.e. editing is supported in general),
-        // but current document  is readonly
-        if (LibreOfficeMainActivity.isExperimentalMode() && LibreOfficeMainActivity.isReadOnlyMode()) {
-            disableMenuItem(R.id.action_save, true);
-            Toast.makeText(mContext, mContext.getString(R.string.temp_file_saving_disabled), Toast.LENGTH_LONG).show();
+        if (LibreOfficeMainActivity.isExperimentalMode()) {
+            boolean enableSaveEntry = !LibreOfficeMainActivity.isReadOnlyMode() && mContext.hasLocationForSave();
+            enableMenuItem(R.id.action_save, enableSaveEntry);
+            if (LibreOfficeMainActivity.isReadOnlyMode()) {
+                // show message in case experimental mode is enabled (i.e. editing is supported in general),
+                // but current document is readonly
+                Toast.makeText(mContext, mContext.getString(R.string.temp_file_saving_disabled), Toast.LENGTH_LONG).show();
+            }
         }
         mMainMenu.findItem(R.id.action_parts).setVisible(mContext.isDrawerEnabled());
     }
diff --git a/android/source/src/java/org/libreoffice/ui/FileUtilities.java b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
index 660fbc0e4528..0d51dd55e1e5 100644
--- a/android/source/src/java/org/libreoffice/ui/FileUtilities.java
+++ b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
@@ -33,11 +33,6 @@ public class FileUtilities {
 
     static final int UNKNOWN = 10;
 
-    public static final String DEFAULT_WRITER_EXTENSION = ".odt";
-    public static final String DEFAULT_IMPRESS_EXTENSION = ".odp";
-    public static final String DEFAULT_SPREADSHEET_EXTENSION = ".ods";
-    public static final String DEFAULT_DRAWING_EXTENSION = ".odg";
-
     public static final String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text";
     public static final String MIMETYPE_OPENDOCUMENT_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet";
     public static final String MIMETYPE_OPENDOCUMENT_PRESENTATION = "application/vnd.oasis.opendocument.presentation";
diff --git a/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java b/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java
index 9f1cd7a9089d..110123f54acf 100644
--- a/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java
+++ b/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java
@@ -23,7 +23,6 @@ import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
-import android.support.annotation.NonNull;
 import android.support.design.widget.FloatingActionButton;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.content.ContextCompat;
@@ -66,9 +65,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
         INVALID
     }
 
-    private String LOGTAG = LibreOfficeUIActivity.class.getSimpleName();
-
-    private DocumentType newDocType = DocumentType.INVALID;
+    private static final String LOGTAG = LibreOfficeUIActivity.class.getSimpleName();
 
     public static final String EXPLORER_PREFS_KEY = "EXPLORER_PREFS";
     private static final String RECENT_DOCUMENTS_KEY = "RECENT_DOCUMENT_URIS";
@@ -123,7 +120,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
     };
 
     private static final int REQUEST_CODE_OPEN_FILECHOOSER = 12345;
-    private static final int REQUEST_CODE_CREATE_NEW_DOCUMENT = 12346;
 
     private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0;
 
@@ -257,10 +253,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
         if (requestCode == REQUEST_CODE_OPEN_FILECHOOSER && resultCode == RESULT_OK) {
             final Uri fileUri = data.getData();
             openDocument(fileUri);
-        } else if (requestCode == REQUEST_CODE_CREATE_NEW_DOCUMENT && resultCode == RESULT_OK) {
-            // "forward" to LibreOfficeMainActivity to create + open the file
-            final Uri fileUri = data.getData();
-            loadNewDocument(newDocType, fileUri);
         }
     }
 
@@ -295,33 +287,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
         startActivity(intent);
     }
 
-    private void createNewFileDialog() {
-        final String extension;
-        if (newDocType == DocumentType.WRITER) {
-            extension = FileUtilities.DEFAULT_WRITER_EXTENSION;
-        } else if (newDocType == DocumentType.CALC) {
-            extension = FileUtilities.DEFAULT_SPREADSHEET_EXTENSION;
-        } else if (newDocType == DocumentType.IMPRESS) {
-            extension = FileUtilities.DEFAULT_IMPRESS_EXTENSION;
-        } else if (newDocType == DocumentType.DRAW) {
-            extension = FileUtilities.DEFAULT_DRAWING_EXTENSION;
-        } else {
-            Log.e(LOGTAG, "Invalid document type passed.");
-            return;
-        }
-
-        String defaultFileName = getString(R.string.default_document_name) + extension;
-        String mimeType = FileUtilities.getMimeType(defaultFileName);
-
-        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
-        intent.addCategory(Intent.CATEGORY_OPENABLE);
-        intent.setType(mimeType);
-        intent.putExtra(Intent.EXTRA_TITLE, defaultFileName);
-
-        startActivityForResult(intent, REQUEST_CODE_CREATE_NEW_DOCUMENT);
-    }
-
-    private void loadNewDocument(DocumentType docType, Uri newFileUri) {
+    private void loadNewDocument(DocumentType docType) {
         final String newDocumentType;
         if (docType == DocumentType.WRITER) {
             newDocumentType = NEW_WRITER_STRING_KEY;
@@ -338,7 +304,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
 
         Intent intent = new Intent(LibreOfficeUIActivity.this, LibreOfficeMainActivity.class);
         intent.putExtra(NEW_DOC_TYPE_KEY, newDocumentType);
-        intent.setData(newFileUri);
         startActivity(intent);
     }
 
@@ -493,20 +458,16 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings
                 showSystemFilePickerAndOpenFile();
                 break;
             case R.id.newWriterFAB:
-                newDocType = DocumentType.WRITER;
-                createNewFileDialog();
+                loadNewDocument(DocumentType.WRITER);
                 break;
             case R.id.newImpressFAB:
-                newDocType = DocumentType.IMPRESS;
-                createNewFileDialog();
+                loadNewDocument(DocumentType.IMPRESS);
                 break;
             case R.id.newCalcFAB:
-                newDocType = DocumentType.CALC;
-                createNewFileDialog();
+                loadNewDocument(DocumentType.CALC);
                 break;
             case R.id.newDrawFAB:
-                newDocType = DocumentType.DRAW;
-                createNewFileDialog();
+                loadNewDocument(DocumentType.DRAW);
                 break;
         }
     }
commit 926acbf45578a3f2cd795c41069318ed391c723b
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 15:49:40 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:17:30 2021 +0200

    android: Drop unused 'FileUtilities#doAccept'
    
    Change-Id: I8b7f268862c3800012548376b3e60ac1f7dca9e3
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114151
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit 865ec16158b04cf98efae4309b40244a7e41eb59)

diff --git a/android/source/src/java/org/libreoffice/ui/FileUtilities.java b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
index f1581a678c30..660fbc0e4528 100644
--- a/android/source/src/java/org/libreoffice/ui/FileUtilities.java
+++ b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
@@ -153,23 +153,6 @@ public class FileUtilities {
         return mime;
     }
 
-    // Filter by mode, and/or in future by filename/wildcard
-    private static boolean doAccept(String filename, int byMode, String byFilename) {
-        Log.d(LOGTAG, "doAccept : " + filename + " mode " + byMode + " byFilename " + byFilename);
-        if (filename == null)
-            return false;
-
-        // check extension
-        if (byMode != ALL) {
-            if (mExtnMap.get (getExtension (filename)) != byMode)
-                return false;
-        }
-        if (!byFilename.equals("")) {
-            // FIXME return false on a non-match
-        }
-        return true;
-    }
-
     static boolean isHidden(File file) {
         return file.getName().startsWith(".");
     }
commit f9820cbde80b8b9b3875996b0b8e6fc965418220
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 14:31:43 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:17:22 2021 +0200

    android: Allow printing and PDF export regardless of mode
    
    Don't only enable the "Export To PDF" and "Print" menu
    items if the experimental editing mode is enabled, but
    always offer them, since they should be sufficiently stable
    and don't require any editing of the document.
    
    To do so, move them into a new menu group
    "group_misc_actions", and move the entry for sending
    UNO commands up, so it remains in the
    "group_edit_actions" menu group whose entries are
    hidden unless editing mode is enabled.
    
    Change-Id: I425cf6d0a45306ff48b45dad6fd0e804b87a2546
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114147
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit 5306ecda9ceb5f9702c2ce6dc6207a3bd5f14a6a)

diff --git a/android/source/res/menu/main.xml b/android/source/res/menu/main.xml
index 94856d52e759..0570a77fdd7b 100644
--- a/android/source/res/menu/main.xml
+++ b/android/source/res/menu/main.xml
@@ -31,6 +31,9 @@
             android:orderInCategory="100"
             app:showAsAction="always"/>
 
+        <item android:id="@+id/action_UNO_commands"
+            android:title="@string/action_UNO_commands"
+            android:orderInCategory="100" />
 
         <item android:id="@+id/action_save"
             android:title="@string/action_save"
@@ -39,6 +42,11 @@
         <item android:id="@+id/action_save_as"
             android:title="@string/action_save_as"
             android:orderInCategory="100" />
+    </group>
+
+    <group android:id="@+id/group_misc_actions"
+           tools:visible="true"
+           android:visible="false">
 
         <item android:id="@+id/action_exportToPDF"
             android:title="@string/action_exportToPDF"
@@ -51,9 +59,6 @@
             android:orderInCategory="100"
             android:visible="true" />
 
-        <item android:id="@+id/action_UNO_commands"
-            android:title="@string/action_UNO_commands"
-            android:orderInCategory="100" />
     </group>
 
     <group android:id="@+id/group_spreadsheet_options"
commit bcf3da6c2929810ce7b0ebddbbe9709381c8503a
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 13:47:23 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:17:11 2021 +0200

    android: Merge 2 'LOKitTileProvider#printDocument' methods
    
    ... and move the check for a new enough SDK version to
    the beginning.
    
    Change-Id: I7f5528985b8c43e218b88899409fdd22b640f72e
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114145
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit 40f30020b9e91d15c4d90e53b1d2e41770fbc58c)

diff --git a/android/source/src/java/org/libreoffice/LOKitTileProvider.java b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
index 5d8d2e557d0f..a5222f7e46ea 100644
--- a/android/source/src/java/org/libreoffice/LOKitTileProvider.java
+++ b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
@@ -340,25 +340,22 @@ class LOKitTileProvider implements TileProvider {
     }
 
     public void printDocument() {
+        if (Build.VERSION.SDK_INT < 19) {
+            mContext.showCustomStatusMessage(mContext.getString(R.string.printing_not_supported));
+            return;
+        }
+
         String mInputFileName = (new File(mInputFile)).getName();
         String file = mInputFileName.substring(0,(mInputFileName.length()-3))+"pdf";
         String cacheFile = mContext.getExternalCacheDir().getAbsolutePath() + "/" + file;
         mDocument.saveAs("file://"+cacheFile,"pdf","");
-        printDocument(cacheFile);
-    }
-
-    private void printDocument(String cacheFile) {
-        if (Build.VERSION.SDK_INT >= 19) {
-            try {
-                PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE);
-                PrintDocumentAdapter printAdapter = new PDFDocumentAdapter(mContext, cacheFile);
-                printManager.print("Document", printAdapter, new PrintAttributes.Builder().build());
+        try {
+            PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE);
+            PrintDocumentAdapter printAdapter = new PDFDocumentAdapter(mContext, cacheFile);
+            printManager.print("Document", printAdapter, new PrintAttributes.Builder().build());
 
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        } else {
-            mContext.showCustomStatusMessage(mContext.getString(R.string.printing_not_supported));
+        } catch (Exception e) {
+            e.printStackTrace();
         }
     }
 
commit 9c477caa39f2326e9fff4dc0cd9df2f6a3af4362
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 11:22:46 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:17:02 2021 +0200

    android: Ask where to save PDF file on export
    
    In Android Viewer, show a file picker to select
    where to save the PDF file on PDF export, rather
    than unconditionally trying to save in the
    "Documents" directory.
    
    This also means that permission
    'android.PERMISSION_WRITE_EXTERNAL_STORAGE' is
    now no longer needed for this task, s. commit
    message from
    
        commit 7d9db806d65cb814af1e99a1e79c3db5aa7c17d5
        Date:   Fri Apr 9 11:24:16 2021 +0200
    
            android: Request PERMISSION_WRITE_EXTERNAL_STORAGE again
    
    for more details.
    
    Also, adapt the 'TileKitProvider#saveDocumentAs'
    methods to return a boolean value indicating
    whether the save operation was successful,
    and trigger showing the message right into
    'LibreOfficeMainActivity#exportToPDF'.
    
    Rename 'LOKitTileProvider#exportToPDF(boolean print)' to
    just 'LOKitTileProvider#printDocument()', since the only
    remaining use case for which it is used is printing now.
    
    Change-Id: I779d782813ca01640811690a388a4b7fd3db4b2a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114143
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit 9ebcb80e2e4335fca1e137d015fe4d84631e282a)

diff --git a/android/source/res/values-de/strings.xml b/android/source/res/values-de/strings.xml
index c2c33ff5b5e5..36effc9303f0 100644
--- a/android/source/res/values-de/strings.xml
+++ b/android/source/res/values-de/strings.xml
@@ -153,9 +153,9 @@
     <string name="current_uno_command">Aktuelles UNO-Kommando</string>
     <string name="display_language">Anzeigesprache</string>
     <string name="display_language_summary">Wählen Sie die Standard-Anzeigesprache</string>
+    <string name="pdf_export_finished">PDF-Export abgeschlossen</string>
     <string name="unable_to_export_pdf">PDF-Export nicht möglich</string>
     <string name="unable_to_save">Speichern nicht möglich</string>
-    <string name="pdf_exported_at">PDF exportiert nach</string>
     <string name="printing_not_supported">Ihr Gerät unterstützt Drucken nicht</string>
     <string name="creating_new_files_not_supported">Erstellen neuer Dokumente auf diesem Gerät nicht verfügbar, benötigt Android-SDK-Version >= 19.</string>
     <string name="error">Fehler</string>
diff --git a/android/source/res/values-tr/strings.xml b/android/source/res/values-tr/strings.xml
index 72d15c210933..a780a59fe165 100644
--- a/android/source/res/values-tr/strings.xml
+++ b/android/source/res/values-tr/strings.xml
@@ -149,7 +149,6 @@
     <string name="display_language">Uygulama Dili</string>
     <string name="display_language_summary">Varsayılan dili değiştir</string>
     <string name="unable_to_export_pdf">Pdf dışa aktarılamıyor.</string>
-    <string name="pdf_exported_at">Şu konumda pdf\'e aktarıldı: </string>
     <string name="printing_not_supported">Cihazınız yazdırmayı desteklemiyor.</string>
     <string name="error">Hata</string>
     <string name="enter_part_name">Bölüm ismi girin.</string>
diff --git a/android/source/res/values/strings.xml b/android/source/res/values/strings.xml
index d0014646bc15..6b3db68e23b6 100644
--- a/android/source/res/values/strings.xml
+++ b/android/source/res/values/strings.xml
@@ -152,9 +152,9 @@
     <string name="current_uno_command">Current UNO command</string>
     <string name="display_language">Display Language</string>
     <string name="display_language_summary">Set the default display language</string>
+    <string name="pdf_export_finished">PDF export finished</string>
     <string name="unable_to_export_pdf">Unable to export to pdf</string>
     <string name="unable_to_save">Unable to save file</string>
-    <string name="pdf_exported_at">Exported to PDF at</string>
     <string name="printing_not_supported">Your device does not support printing</string>
     <string name="creating_new_files_not_supported">Creating new files not supported on this device, requires Android SDK version >= 19.</string>
     <string name="error">Error</string>
diff --git a/android/source/src/java/org/libreoffice/LOKitTileProvider.java b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
index 5f4684703f36..5d8d2e557d0f 100644
--- a/android/source/src/java/org/libreoffice/LOKitTileProvider.java
+++ b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
@@ -277,7 +277,7 @@ class LOKitTileProvider implements TileProvider {
     }
 
     @Override
-    public void saveDocumentAs(final String filePath, String format, boolean takeOwnership) {
+    public boolean saveDocumentAs(final String filePath, String format, boolean takeOwnership) {
         String options = "";
         if (takeOwnership) {
             options = "TakeOwnership";
@@ -287,21 +287,16 @@ class LOKitTileProvider implements TileProvider {
         Log.d("saveFilePathURL", newFilePath);
         LOKitShell.showProgressSpinner(mContext);
         mDocument.saveAs(newFilePath, format, options);
+        final boolean ok;
         if (!mOffice.getError().isEmpty()){
+            ok = true;
             Log.e("Save Error", mOffice.getError());
             if (format.equals("svg")) {
                 // error in creating temp slideshow svg file
                 Log.d(LOGTAG, "Error in creating temp slideshow svg file");
             } else if(format.equals("pdf")){
                 Log.d(LOGTAG, "Error in creating pdf file");
-                LOKitShell.getMainHandler().post(new Runnable() {
-                    @Override
-                    public void run() {
-                        // There was some error
-                        mContext.showCustomStatusMessage(mContext.getString(R.string.unable_to_export_pdf));
-                    }
-                });
-            }else {
+            } else {
                 LOKitShell.getMainHandler().post(new Runnable() {
                     @Override
                     public void run() {
@@ -311,6 +306,7 @@ class LOKitTileProvider implements TileProvider {
                 });
             }
         } else {
+            ok = false;
             if (format.equals("svg")) {
                 // successfully created temp slideshow svg file
                 LOKitShell.getMainHandler().post(new Runnable() {
@@ -319,52 +315,36 @@ class LOKitTileProvider implements TileProvider {
                         mContext.startPresentation(newFilePath);
                     }
                 });
-            }else if(format.equals("pdf")){
-                LOKitShell.getMainHandler().post(new Runnable() {
-                    @Override
-                    public void run() {
-                        // There was no error
-                        mContext.showCustomStatusMessage(mContext.getString(R.string.pdf_exported_at)+filePath);
-                    }
-                });
             } else if (takeOwnership) {
                 mInputFile = filePath;
             }
         }
         LOKitShell.hideProgressSpinner(mContext);
+        return ok;
     }
 
     @Override
-    public void saveDocumentAs(final String filePath, boolean takeOwnership) {
+    public boolean saveDocumentAs(final String filePath, boolean takeOwnership) {
         final int docType = mDocument.getDocumentType();
         if (docType == Document.DOCTYPE_TEXT)
-            saveDocumentAs(filePath, "odt", takeOwnership);
+            return saveDocumentAs(filePath, "odt", takeOwnership);
         else if (docType == Document.DOCTYPE_SPREADSHEET)
-            saveDocumentAs(filePath, "ods", takeOwnership);
+            return saveDocumentAs(filePath, "ods", takeOwnership);
         else if (docType == Document.DOCTYPE_PRESENTATION)
-            saveDocumentAs(filePath, "odp", takeOwnership);
+            return saveDocumentAs(filePath, "odp", takeOwnership);
         else if (docType == Document.DOCTYPE_DRAWING)
-            saveDocumentAs(filePath, "odg", takeOwnership);
-        else
-            Log.w(LOGTAG, "Cannot determine file format from document. Not saving.");
+            return saveDocumentAs(filePath, "odg", takeOwnership);
+
+        Log.w(LOGTAG, "Cannot determine file format from document. Not saving.");
+        return false;
     }
 
-    public void exportToPDF(boolean print){
-        String dir = Environment.getExternalStorageDirectory().getAbsolutePath()+"/Documents";
-        File docDir = new File(dir);
-        if(!docDir.exists()){
-            docDir.mkdir();
-        }
+    public void printDocument() {
         String mInputFileName = (new File(mInputFile)).getName();
         String file = mInputFileName.substring(0,(mInputFileName.length()-3))+"pdf";
-        if(print){
-            String cacheFile = mContext.getExternalCacheDir().getAbsolutePath()
-                    + "/" + file;
-            mDocument.saveAs("file://"+cacheFile,"pdf","");
-            printDocument(cacheFile);
-        }else{
-            saveDocumentAs(dir+"/"+file,"pdf", false);
-        }
+        String cacheFile = mContext.getExternalCacheDir().getAbsolutePath() + "/" + file;
+        mDocument.saveAs("file://"+cacheFile,"pdf","");
+        printDocument(cacheFile);
     }
 
     private void printDocument(String cacheFile) {
diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
index 255c4d5bdd95..1812ad253744 100644
--- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -64,6 +64,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
     private static final String ASSETS_EXTRACTED_PREFS_KEY = "ASSETS_EXTRACTED";
     private static final String ENABLE_DEVELOPER_PREFS_KEY = "ENABLE_DEVELOPER";
     private static final int REQUEST_CODE_SAVEAS = 12345;
+    private static final int REQUEST_CODE_EXPORT_TO_PDF = 12346;
 
     //TODO "public static" is a temporary workaround
     public static LOKitThread loKitThread;
@@ -340,6 +341,46 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         toolbarTop.setTitle(displayName);
     }
 
+    public void exportToPDF() {
+        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType(FileUtilities.MIMETYPE_PDF);
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentUri);
+
+        startActivityForResult(intent, REQUEST_CODE_EXPORT_TO_PDF);
+    }
+
+    private void exportToPDF(final Uri uri) {
+        boolean exportOK = false;
+        File tempFile = null;
+        try {
+            tempFile = File.createTempFile("LibreOffice_", ".pdf");
+            mTileProvider.saveDocumentAs(tempFile.getAbsolutePath(),"pdf", false);
+
+            try {
+                FileInputStream inputStream = new FileInputStream(tempFile);
+                exportOK = copyStreamToUri(inputStream, uri);
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (tempFile != null && tempFile.exists()) {
+                tempFile.delete();
+            }
+        }
+
+        final int msgId = exportOK ? R.string.pdf_export_finished : R.string.unable_to_export_pdf;
+        LOKitShell.getMainHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                showCustomStatusMessage(getString(msgId));
+            }
+        });
+    }
+
     /**
      * Returns the ODF MIME type that can be used for the current document,
      * regardless of whether the document is an ODF Document or not
@@ -1046,6 +1087,9 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         if (requestCode == REQUEST_CODE_SAVEAS && resultCode == RESULT_OK) {
             final Uri fileUri = data.getData();
             saveDocumentAs(fileUri);
+        } else if (requestCode == REQUEST_CODE_EXPORT_TO_PDF && resultCode == RESULT_OK) {
+            final Uri fileUri = data.getData();
+            exportToPDF(fileUri);
         } else {
             mFormattingController.handleActivityResult(requestCode, resultCode, data);
             hideBottomToolbar();
diff --git a/android/source/src/java/org/libreoffice/TileProvider.java b/android/source/src/java/org/libreoffice/TileProvider.java
index ea93d5b5c803..c979a9883c13 100644
--- a/android/source/src/java/org/libreoffice/TileProvider.java
+++ b/android/source/src/java/org/libreoffice/TileProvider.java
@@ -29,8 +29,9 @@ public interface TileProvider {
      *                      as compared to just saving a copy of the current document
      *                      or exporting to a different file format.
      *                      Must be 'false' when using this method for export to e.g. PNG or PDF.
+     * @return Whether saving was successful.
      */
-    void saveDocumentAs(String filePath, String format, boolean takeOwnership);
+    boolean saveDocumentAs(String filePath, String format, boolean takeOwnership);
 
     /**
      * Saves the current document under the given path,
@@ -38,7 +39,7 @@ public interface TileProvider {
      * @param takeOwnership (s. documentation for
      *                      'saveDocumentAs(String filePath, String format, boolean takeOwnership)')
      */
-    void saveDocumentAs(String filePath, boolean takeOwnership);
+    boolean saveDocumentAs(String filePath, boolean takeOwnership);
 
     /**
      * Returns the page width in pixels.
diff --git a/android/source/src/java/org/libreoffice/ToolbarController.java b/android/source/src/java/org/libreoffice/ToolbarController.java
index ceea83a2b311..1384339f8c30 100644
--- a/android/source/src/java/org/libreoffice/ToolbarController.java
+++ b/android/source/src/java/org/libreoffice/ToolbarController.java
@@ -181,10 +181,10 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener {
                 mContext.openDrawer();
                 return true;
             case R.id.action_exportToPDF:
-                mContext.getTileProvider().exportToPDF(false);
+                mContext.exportToPDF();
                 return true;
             case R.id.action_print:
-                mContext.getTileProvider().exportToPDF(true);
+                mContext.getTileProvider().printDocument();
                 return true;
             case R.id.action_settings:
                 mContext.showSettings();
diff --git a/android/source/src/java/org/libreoffice/ui/FileUtilities.java b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
index 8422e845dd40..f1581a678c30 100644
--- a/android/source/src/java/org/libreoffice/ui/FileUtilities.java
+++ b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
@@ -42,6 +42,7 @@ public class FileUtilities {
     public static final String MIMETYPE_OPENDOCUMENT_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet";
     public static final String MIMETYPE_OPENDOCUMENT_PRESENTATION = "application/vnd.oasis.opendocument.presentation";
     public static final String MIMETYPE_OPENDOCUMENT_GRAPHICS = "application/vnd.oasis.opendocument.graphics";
+    public static final String MIMETYPE_PDF = "application/pdf";
 
     private static final Map<String, Integer> mExtnMap = new HashMap<String, Integer>();
     private static final Map<String, String> extensionToMimeTypeMap = new HashMap<String, String>();
commit 5a373d0ea8ba70e5b3ce3f7fb406a1f74f403ad8
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 08:57:56 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:16:52 2021 +0200

    android: Add a "Save As..." menu entry
    
    This adds a "Save As..." menu entry to Android
    Viewer in order to save the currently opened file
    to a different location.
    
    Currently, the file is always saved in the corresponding
    ODF format, regardless of the original file type,
    i.e. that e.g. a DOCX file is saved in ODT format.
    (This could be extended to allow a selection of the
    target format as needed.)
    
    Like "Save As" (and as compared to "Save a Copy")
    in the desktop version, the app remembers the
    new document URI and subsequent save operations
    will overwrite the newly saved file, not the originally
    opened one.
    (There is no need to create a new temporary
    local file to use, though.)
    
    The directory of the currently used file
    is preselected in the file chooser used to
    specify where to save the new file.
    
    Make sure to copy the file in a non-main thread,
    since the destination URI might be handled
    by a DocumentsProvider that does network access.
    However, for now the main thread just waits for
    the separate thread to finish, just like
    
        commit 7f838b73e85eb6f0a1dce4647650a5cf5f34ccd2
        Date:   Fri Mar 19 15:46:36 2021 +0100
    
            tdf#129833 android: Move reading file to separate thread
    
    implemented it for copying from the URI to the
    temporary file when opening a file.
    
    This also adds a 'TileProvider#isDrawing' method
    (like the already existing 'isTextDocument',
    'isSpreadsheet' and 'isPresentation' ones).
    
    Change-Id: I6f56b71763431b89a6c74be35cc1e81fad136cd0
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114058
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit d16e569209fe40c2cb5776f7b9415e273d5b2387)

diff --git a/android/source/res/menu/main.xml b/android/source/res/menu/main.xml
index 2c97b3201b9e..94856d52e759 100644
--- a/android/source/res/menu/main.xml
+++ b/android/source/res/menu/main.xml
@@ -36,6 +36,10 @@
             android:title="@string/action_save"
             android:orderInCategory="100" />
 
+        <item android:id="@+id/action_save_as"
+            android:title="@string/action_save_as"
+            android:orderInCategory="100" />
+
         <item android:id="@+id/action_exportToPDF"
             android:title="@string/action_exportToPDF"
             android:orderInCategory="100"
diff --git a/android/source/res/values-de/strings.xml b/android/source/res/values-de/strings.xml
index 639ddca4d9ef..c2c33ff5b5e5 100644
--- a/android/source/res/values-de/strings.xml
+++ b/android/source/res/values-de/strings.xml
@@ -48,6 +48,7 @@
     <string name="action_strikeout">Durchgestrichen</string>
     <string name="action_keyboard">Tastatur anzeigen</string>
     <string name="action_save">Speichern</string>
+    <string name="action_save_as">Speichern unter...</string>
     <string name="action_fromat">Format anwenden</string>
     <string name="action_search">Suchen</string>
     <string name="action_UNO_commands">UNO-Kommando senden</string>
diff --git a/android/source/res/values/strings.xml b/android/source/res/values/strings.xml
index 5e7ad1e3ded7..d0014646bc15 100644
--- a/android/source/res/values/strings.xml
+++ b/android/source/res/values/strings.xml
@@ -47,6 +47,7 @@
     <string name="action_strikeout">Strike Out</string>
     <string name="action_keyboard">Show keyboard</string>
     <string name="action_save">Save</string>
+    <string name="action_save_as">Save As...</string>
     <string name="action_fromat">Enable Format</string>
     <string name="action_search">Search</string>
     <string name="action_UNO_commands">Send UNO Cmd</string>
diff --git a/android/source/src/java/org/libreoffice/LOKitTileProvider.java b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
index 2fb3551eada2..5f4684703f36 100644
--- a/android/source/src/java/org/libreoffice/LOKitTileProvider.java
+++ b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
@@ -631,6 +631,14 @@ class LOKitTileProvider implements TileProvider {
         }
     }
 
+    /**
+     * @see TileProvider#isDrawing()
+     */
+    @Override
+    public boolean isDrawing() {
+        return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_DRAWING;
+    }
+
     /**
      * @see TileProvider#isTextDocument()
      */
diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
index d97719afb726..255c4d5bdd95 100644
--- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -14,6 +14,7 @@ import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.preference.PreferenceManager;
+import android.provider.DocumentsContract;
 import android.support.design.widget.BottomSheetBehavior;
 import android.support.design.widget.Snackbar;
 import android.support.v4.widget.DrawerLayout;
@@ -62,6 +63,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
     private static final String ENABLE_EXPERIMENTAL_PREFS_KEY = "ENABLE_EXPERIMENTAL";
     private static final String ASSETS_EXTRACTED_PREFS_KEY = "ASSETS_EXTRACTED";
     private static final String ENABLE_DEVELOPER_PREFS_KEY = "ENABLE_DEVELOPER";
+    private static final int REQUEST_CODE_SAVEAS = 12345;
 
     //TODO "public static" is a temporary workaround
     public static LOKitThread loKitThread;
@@ -309,6 +311,56 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:Save", true));
     }
 
+    /**
+     * Open file chooser and save the document to the URI
+     * selected there.
+     */
+    public void saveDocumentAs() {
+        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        String mimeType = getODFMimeTypeForDocument();
+        intent.setType(mimeType);
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentUri);
+
+        startActivityForResult(intent, REQUEST_CODE_SAVEAS);
+    }
+
+    /**
+     * Saves the document under the given URI using ODF format
+     * and uses that URI from now on for all operations.
+     * @param newUri URI to save the document and use from now on.
+     */
+    private void saveDocumentAs(Uri newUri) {
+        mDocumentUri = newUri;
+        // save in ODF format
+        mTileProvider.saveDocumentAs(mTempFile.getPath(), true);
+        saveFileToOriginalSource();
+
+        String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), mDocumentUri);
+        toolbarTop.setTitle(displayName);
+    }
+
+    /**
+     * Returns the ODF MIME type that can be used for the current document,
+     * regardless of whether the document is an ODF Document or not
+     * (e.g. returns FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT for a DOCX file).
+     * @return MIME type, or empty string, if no appropriate MIME type could be found.
+     */
+    private String getODFMimeTypeForDocument() {
+        if (mTileProvider.isTextDocument())
+            return FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT;
+        else if (mTileProvider.isSpreadsheet())
+            return FileUtilities.MIMETYPE_OPENDOCUMENT_SPREADSHEET;
+        else if (mTileProvider.isPresentation())
+            return FileUtilities.MIMETYPE_OPENDOCUMENT_PRESENTATION;
+        else if (mTileProvider.isDrawing())
+            return FileUtilities.MIMETYPE_OPENDOCUMENT_GRAPHICS;
+        else {
+            Log.w(LOGTAG, "Cannot determine MIME type to use.");
+            return "";
+        }
+    }
+
     public void saveFileToOriginalSource() {
         if (isReadOnlyMode() || mTempFile == null || mDocumentUri == null || !mDocumentUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))
             return;
@@ -316,8 +368,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         boolean copyOK = false;
         try {
             final FileInputStream inputStream = new FileInputStream(mTempFile);
-            final OutputStream outputStream = getContentResolver().openOutputStream(mDocumentUri);
-            copyOK = copyStream(inputStream, outputStream);
+            copyOK = copyStreamToUri(inputStream, mDocumentUri);
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         }
@@ -922,6 +973,40 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         return copyThread.result;
     }
 
+    /**
+     * Copies everything from the given InputStream to the given URI and closes the
+     * InputStream in the end.
+     * @see LibreOfficeMainActivity#copyUriToStream(Uri, OutputStream)
+     *      which does the same thing the other way around.
+     */
+    private boolean copyStreamToUri(final InputStream inputStream, final Uri outputUri) {
+        class CopyThread extends Thread {
+            /** Whether copy operation was successful. */
+            private boolean result = false;
+
+            @Override
+            public void run() {
+                final ContentResolver contentResolver = getContentResolver();
+                try {
+                    OutputStream outputStream = contentResolver.openOutputStream(outputUri);
+                    result = copyStream(inputStream, outputStream);
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        CopyThread copyThread = new CopyThread();
+        copyThread.start();
+        try {
+            // wait for copy operation to finish
+            // NOTE: might be useful to add some indicator in UI for long copy operations involving network...
+            copyThread.join();
+        } catch(InterruptedException e) {
+            e.printStackTrace();
+        }
+        return copyThread.result;
+    }
+
     public void showCustomStatusMessage(String message){
         Snackbar.make(mDrawerLayout, message, Snackbar.LENGTH_LONG).show();
     }
@@ -958,8 +1043,13 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        mFormattingController.handleActivityResult(requestCode, resultCode, data);
-        hideBottomToolbar();
+        if (requestCode == REQUEST_CODE_SAVEAS && resultCode == RESULT_OK) {
+            final Uri fileUri = data.getData();
+            saveDocumentAs(fileUri);
+        } else {
+            mFormattingController.handleActivityResult(requestCode, resultCode, data);
+            hideBottomToolbar();
+        }
     }
 }
 
diff --git a/android/source/src/java/org/libreoffice/TileProvider.java b/android/source/src/java/org/libreoffice/TileProvider.java
index 1a20c8b080d0..ea93d5b5c803 100644
--- a/android/source/src/java/org/libreoffice/TileProvider.java
+++ b/android/source/src/java/org/libreoffice/TileProvider.java
@@ -85,6 +85,11 @@ public interface TileProvider {
      */
     void close();
 
+    /**
+     * Returns true if the current open document is a drawing.
+     */
+    boolean isDrawing();
+
     /**
      * Returns true if the current open document is a text document.
      */
diff --git a/android/source/src/java/org/libreoffice/ToolbarController.java b/android/source/src/java/org/libreoffice/ToolbarController.java
index 308bc9e6b254..ceea83a2b311 100644
--- a/android/source/src/java/org/libreoffice/ToolbarController.java
+++ b/android/source/src/java/org/libreoffice/ToolbarController.java
@@ -174,6 +174,9 @@ public class ToolbarController implements Toolbar.OnMenuItemClickListener {
             case R.id.action_save:
                 mContext.getTileProvider().saveDocument();
                 return true;
+            case R.id.action_save_as:
+                mContext.saveDocumentAs();
+                return true;
             case R.id.action_parts:
                 mContext.openDrawer();
                 return true;
diff --git a/android/source/src/java/org/libreoffice/ui/FileUtilities.java b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
index 5bcc48d31141..8422e845dd40 100644
--- a/android/source/src/java/org/libreoffice/ui/FileUtilities.java
+++ b/android/source/src/java/org/libreoffice/ui/FileUtilities.java
@@ -38,6 +38,11 @@ public class FileUtilities {
     public static final String DEFAULT_SPREADSHEET_EXTENSION = ".ods";
     public static final String DEFAULT_DRAWING_EXTENSION = ".odg";
 
+    public static final String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text";
+    public static final String MIMETYPE_OPENDOCUMENT_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet";
+    public static final String MIMETYPE_OPENDOCUMENT_PRESENTATION = "application/vnd.oasis.opendocument.presentation";
+    public static final String MIMETYPE_OPENDOCUMENT_GRAPHICS = "application/vnd.oasis.opendocument.graphics";
+
     private static final Map<String, Integer> mExtnMap = new HashMap<String, Integer>();
     private static final Map<String, String> extensionToMimeTypeMap = new HashMap<String, String>();
     static {
@@ -101,14 +106,14 @@ public class FileUtilities {
         // Android's MimeTypeMap lacks some types that we need
         extensionToMimeTypeMap.put("odb", "application/vnd.oasis.opendocument.database");
         extensionToMimeTypeMap.put("odf", "application/vnd.oasis.opendocument.formula");
-        extensionToMimeTypeMap.put("odg", "application/vnd.oasis.opendocument.graphics");
+        extensionToMimeTypeMap.put("odg", MIMETYPE_OPENDOCUMENT_GRAPHICS);
         extensionToMimeTypeMap.put("otg", "application/vnd.oasis.opendocument.graphics-template");
         extensionToMimeTypeMap.put("odi", "application/vnd.oasis.opendocument.image");
-        extensionToMimeTypeMap.put("odp", "application/vnd.oasis.opendocument.presentation");
+        extensionToMimeTypeMap.put("odp", MIMETYPE_OPENDOCUMENT_PRESENTATION);
         extensionToMimeTypeMap.put("otp", "application/vnd.oasis.opendocument.presentation-template");
-        extensionToMimeTypeMap.put("ods", "application/vnd.oasis.opendocument.spreadsheet");
+        extensionToMimeTypeMap.put("ods", MIMETYPE_OPENDOCUMENT_SPREADSHEET);
         extensionToMimeTypeMap.put("ots", "application/vnd.oasis.opendocument.spreadsheet-template");
-        extensionToMimeTypeMap.put("odt", "application/vnd.oasis.opendocument.text");
+        extensionToMimeTypeMap.put("odt", MIMETYPE_OPENDOCUMENT_TEXT);
         extensionToMimeTypeMap.put("odm", "application/vnd.oasis.opendocument.text-master");
         extensionToMimeTypeMap.put("ott", "application/vnd.oasis.opendocument.text-template");
         extensionToMimeTypeMap.put("oth", "application/vnd.oasis.opendocument.text-web");
commit 514e2b142f8c2d015996e333663ae4eb20bdbc65
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Thu Apr 15 08:38:46 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:16:43 2021 +0200

    android: Extract copying Uri to stream in thread to separate method
    
    This essentially extracts what
    
        commit 7f838b73e85eb6f0a1dce4647650a5cf5f34ccd2
        Date:   Fri Mar 19 15:46:36 2021 +0100
    
            tdf#129833 android: Move reading file to separate thread
    
    introduced into a separate helper method.
    
    Change-Id: Ic70ba9f2e2bc125415ff1b3fa3375c3389181c43
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114123
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit a2b4564d719e6efeb614052b9c833991e447c68c)

diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
index a3f62601c1c4..d97719afb726 100644
--- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -282,7 +282,6 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
     }
 
     private boolean copyFileToTemp() {
-        final ContentResolver contentResolver = getContentResolver();
         // CSV files need a .csv suffix to be opened in Calc.
         String suffix = null;
         String intentType = getIntent().getType();
@@ -293,36 +292,7 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         try {
             mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir());
             final FileOutputStream outputStream = new FileOutputStream(mTempFile);
-            // need to run copy operation in a separate thread, since network access is not
-            // allowed from main thread, but that may happen here when underlying
-            // DocumentsProvider (like the NextCloud one) does that
-            class CopyThread extends Thread {
-                /** Whether copy operation was successful. */
-                private boolean result = false;
-
-                @Override
-                public void run() {
-                    result = false;
-                    try {
-                        InputStream inputStream = contentResolver.openInputStream(mDocumentUri);
-                        result = copyStream(inputStream, outputStream);
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                        return;
-                    }
-                }
-            };
-            CopyThread copyThread = new CopyThread();
-            copyThread.start();
-            try {
-                // wait for copy operation to finish
-                // NOTE: might be useful to add some indicator in UI for long copy operations involving network...
-                copyThread.join();
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-
-            return copyThread.result;
+            return copyUriToStream(mDocumentUri, outputStream);
         } catch (FileNotFoundException e) {
             return false;
         } catch (IOException e) {
@@ -915,6 +885,43 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         }
     }
 
+    /**
+     * Copies everything from the given Uri to the given OutputStream
+     * and closes the OutputStream in the end.
+     * The copy operation runs in a separate thread, but the method only returns
+     * after the thread has finished its execution.
+     * This can be used to copy in a blocking way when network access is involved,
+     * which is not allowed from the main thread, but that may happen when an underlying
+     * DocumentsProvider (like the NextCloud one) does network access.
+     */
+    private boolean copyUriToStream(final Uri inputUri, final OutputStream outputStream) {
+        class CopyThread extends Thread {
+            /** Whether copy operation was successful. */
+            private boolean result = false;
+
+            @Override
+            public void run() {
+                final ContentResolver contentResolver = getContentResolver();
+                try {
+                    InputStream inputStream = contentResolver.openInputStream(inputUri);
+                    result = copyStream(inputStream, outputStream);
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        CopyThread copyThread = new CopyThread();
+        copyThread.start();
+        try {
+            // wait for copy operation to finish
+            // NOTE: might be useful to add some indicator in UI for long copy operations involving network...
+            copyThread.join();
+        } catch(InterruptedException e) {
+            e.printStackTrace();
+        }
+        return copyThread.result;
+    }
+
     public void showCustomStatusMessage(String message){
         Snackbar.make(mDrawerLayout, message, Snackbar.LENGTH_LONG).show();
     }
commit d8fa465abebd129d500b0dd0b9e1b05ba2c8b99e
Author:     Michael Weghorn <m.weghorn at posteo.de>
AuthorDate: Tue Apr 13 07:55:54 2021 +0200
Commit:     Michael Weghorn <m.weghorn at posteo.de>
CommitDate: Fri Apr 16 10:16:33 2021 +0200

    android: Don't store whether spreadsheet in LibreOfficeMainActivity
    
    LOKitTileProvider has that information, so query it instead
    and don't duplicate information in LibreOfficeMainActivity.
    
    Change-Id: I233986d6e94e5676464cb3399303efd545e33d32
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/114057
    Tested-by: Jenkins
    Reviewed-by: Michael Weghorn <m.weghorn at posteo.de>
    (cherry picked from commit 4db8535fba00c476555e09e32e521993ab77dc4d)

diff --git a/android/source/src/java/org/libreoffice/FontController.java b/android/source/src/java/org/libreoffice/FontController.java
index a00e13e1485c..8729b51e01ac 100644
--- a/android/source/src/java/org/libreoffice/FontController.java
+++ b/android/source/src/java/org/libreoffice/FontController.java
@@ -158,7 +158,7 @@ public class FontController implements AdapterView.OnItemSelectedListener {
             JSONObject valueJson = new JSONObject();
             valueJson.put("type", "long");
             valueJson.put("value", 0x00FFFFFF & color);
-            if(mActivity.isSpreadsheet()){
+            if(mActivity.getTileProvider().isSpreadsheet()){
                 json.put("BackgroundColor", valueJson);
                 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND, ".uno:BackgroundColor", json.toString()));
             }else if(mActivity.getTileProvider().isPresentation()){
diff --git a/android/source/src/java/org/libreoffice/InvalidationHandler.java b/android/source/src/java/org/libreoffice/InvalidationHandler.java
index 588fec9f5372..b74d92d15460 100644
--- a/android/source/src/java/org/libreoffice/InvalidationHandler.java
+++ b/android/source/src/java/org/libreoffice/InvalidationHandler.java
@@ -505,7 +505,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes
                 changeStateTo(OverlayState.TRANSITION);
             }
             mDocumentOverlay.changeSelections(Collections.<RectF>emptyList());
-            if (mContext.isSpreadsheet()) {
+            if (mContext.getTileProvider().isSpreadsheet()) {
                 mDocumentOverlay.showHeaderSelection(null);
             }
             mContext.getToolbarController().showHideClipboardCutAndCopy(false);
@@ -516,7 +516,7 @@ public class InvalidationHandler implements Document.MessageCallback, Office.Mes
             }
             changeStateTo(OverlayState.SELECTION);
             mDocumentOverlay.changeSelections(rectangles);
-            if (mContext.isSpreadsheet()) {
+            if (mContext.getTileProvider().isSpreadsheet()) {
                 mDocumentOverlay.showHeaderSelection(rectangles.get(0));
             }
             String selectedText = mContext.getTileProvider().getTextSelection("");
diff --git a/android/source/src/java/org/libreoffice/LOKitShell.java b/android/source/src/java/org/libreoffice/LOKitShell.java
index 43dd30a69847..5fb7159f1032 100644
--- a/android/source/src/java/org/libreoffice/LOKitShell.java
+++ b/android/source/src/java/org/libreoffice/LOKitShell.java
@@ -27,7 +27,9 @@ public class LOKitShell {
     private static final String LOGTAG = LOKitShell.class.getSimpleName();
 
     public static float getDpi(Context context) {
-        if (((LibreOfficeMainActivity)context).isSpreadsheet()) return 96f;
+        LOKitTileProvider tileProvider = ((LibreOfficeMainActivity)context).getTileProvider();
+        if (tileProvider != null && tileProvider.isSpreadsheet())
+            return 96f;
         DisplayMetrics metrics = context.getResources().getDisplayMetrics();
         return metrics.density * 160;
     }
diff --git a/android/source/src/java/org/libreoffice/LOKitTileProvider.java b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
index e9fc2d52a154..2fb3551eada2 100644
--- a/android/source/src/java/org/libreoffice/LOKitTileProvider.java
+++ b/android/source/src/java/org/libreoffice/LOKitTileProvider.java
@@ -102,10 +102,6 @@ class LOKitTileProvider implements TileProvider {
 
         Log.i(LOGTAG, "====> mDocument = " + mDocument);
 
-        if(isSpreadsheet()) {
-            mContext.setIsSpreadsheet(true); // Calc is treated differently e.g. DPI = 96f
-        }
-
         mDPI = LOKitShell.getDpi(mContext);
         mTileWidth = pixelToTwip(TILE_SIZE, mDPI);
         mTileHeight = pixelToTwip(TILE_SIZE, mDPI);
diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
index f751f2e7dd61..a3f62601c1c4 100644
--- a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
+++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java
@@ -94,7 +94,6 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
     private SearchController mSearchController;
     private UNOCommandsController mUNOCommandsController;
     private CalcHeadersController mCalcHeadersController;
-    private boolean mIsSpreadsheet;
     private LOKitTileProvider mTileProvider;
     private String mPassword;
     private boolean mPasswordProtected;
@@ -815,14 +814,6 @@ public class LibreOfficeMainActivity extends AppCompatActivity implements Settin
         });
     }
 
-    public void setIsSpreadsheet(boolean b) {
-        mIsSpreadsheet = b;
-    }
-
-    public boolean isSpreadsheet() {
-        return mIsSpreadsheet;
-    }
-
     public static boolean isReadOnlyMode() {
         return mbISReadOnlyMode;
     }


More information about the Libreoffice-commits mailing list