From 07c3a7bdbbec886dd0d2635090e5bd483286853f Mon Sep 17 00:00:00 2001 From: Jan Holesovsky Date: Tue, 10 Dec 2019 16:12:52 +0100 Subject: [PATCH] android: Implement uploading back to the cloud storage. All these cases should be covered: * user's explicit save via File -> Save * autosave * autosave on exit Implemented via IntentFilter magic, we can call back from one activity to the other to perform the actual saving in the shell. Change-Id: I97d6e94028a9600a71f030af7146ee01163d09b8 Reviewed-on: https://gerrit.libreoffice.org/84872 Reviewed-by: Jan Holesovsky Tested-by: Jan Holesovsky --- .../androidapp/storage/IDocumentProvider.java | 2 + .../libreoffice/androidapp/storage/IFile.java | 4 +- .../storage/external/ExternalFile.java | 28 ++++-- .../androidapp/storage/local/LocalFile.java | 2 +- .../storage/owncloud/OwnCloudFile.java | 33 +++++-- .../androidapp/ui/LibreOfficeUIActivity.java | 85 +++++++++++++++++-- .../libreoffice/androidlib/LOActivity.java | 19 ++++- 7 files changed, 146 insertions(+), 27 deletions(-) diff --git a/android/app/src/main/java/org/libreoffice/androidapp/storage/IDocumentProvider.java b/android/app/src/main/java/org/libreoffice/androidapp/storage/IDocumentProvider.java index 474610ab1..a16fb07c7 100644 --- a/android/app/src/main/java/org/libreoffice/androidapp/storage/IDocumentProvider.java +++ b/android/app/src/main/java/org/libreoffice/androidapp/storage/IDocumentProvider.java @@ -68,3 +68,5 @@ public interface IDocumentProvider { */ boolean checkProviderAvailability(Context context); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/android/app/src/main/java/org/libreoffice/androidapp/storage/IFile.java b/android/app/src/main/java/org/libreoffice/androidapp/storage/IFile.java index da8067bd0..5817f40b2 100644 --- a/android/app/src/main/java/org/libreoffice/androidapp/storage/IFile.java +++ b/android/app/src/main/java/org/libreoffice/androidapp/storage/IFile.java @@ -112,5 +112,7 @@ public interface IFile { * @param file * A local file pointing to the new version of the document. */ - void saveDocument(File file); + void saveDocument(); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/android/app/src/main/java/org/libreoffice/androidapp/storage/external/ExternalFile.java b/android/app/src/main/java/org/libreoffice/androidapp/storage/external/ExternalFile.java index 6a78f983c..557e7580e 100644 --- a/android/app/src/main/java/org/libreoffice/androidapp/storage/external/ExternalFile.java +++ b/android/app/src/main/java/org/libreoffice/androidapp/storage/external/ExternalFile.java @@ -34,7 +34,10 @@ public class ExternalFile implements IFile{ private ExtsdDocumentsProvider provider; private DocumentFile docFile; - private File duplicateFile; + + /** We create the document just once, cache it for further returning. */ + private File mCachedFile; + private Context context; public ExternalFile(ExtsdDocumentsProvider provider, DocumentFile docFile, Context context) { @@ -112,12 +115,14 @@ public class ExternalFile implements IFile{ @Override public File getDocument() { - if(isDirectory()) { + if (mCachedFile != null) + return mCachedFile; + + if (isDirectory()) return null; - } else { - duplicateFile = duplicateInCache(); - return duplicateFile; - } + + mCachedFile = duplicateInCache(); + return mCachedFile; } private File duplicateInCache() { @@ -138,11 +143,16 @@ public class ExternalFile implements IFile{ } @Override - public void saveDocument(File file) { + public void saveDocument() { + if (mCachedFile == null) { + Log.e(LOGTAG, "Trying to save document that was not created via getDocument()("); + return; + } + try{ OutputStream ostream = context.getContentResolver(). openOutputStream(docFile.getUri()); - InputStream istream = new FileInputStream(file); + InputStream istream = new FileInputStream(mCachedFile); IOUtils.copy(istream, ostream); @@ -162,3 +172,5 @@ public class ExternalFile implements IFile{ } } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/android/app/src/main/java/org/libreoffice/androidapp/storage/local/LocalFile.java b/android/app/src/main/java/org/libreoffice/androidapp/storage/local/LocalFile.java index 0594c7272..bf1661047 100644 --- a/android/app/src/main/java/org/libreoffice/androidapp/storage/local/LocalFile.java +++ b/android/app/src/main/java/org/libreoffice/androidapp/storage/local/LocalFile.java @@ -97,7 +97,7 @@ public class LocalFile implements IFile { } @Override - public void saveDocument(File file) { + public void saveDocument() { // do nothing; file is local } } diff --git a/android/app/src/main/java/org/libreoffice/androidapp/storage/owncloud/OwnCloudFile.java b/android/app/src/main/java/org/libreoffice/androidapp/storage/owncloud/OwnCloudFile.java index cc7589fae..4683f81f9 100644 --- a/android/app/src/main/java/org/libreoffice/androidapp/storage/owncloud/OwnCloudFile.java +++ b/android/app/src/main/java/org/libreoffice/androidapp/storage/owncloud/OwnCloudFile.java @@ -1,6 +1,7 @@ package org.libreoffice.androidapp.storage.owncloud; import android.content.Context; +import android.util.Log; import java.io.File; import java.io.FileFilter; @@ -25,10 +26,14 @@ import com.owncloud.android.lib.resources.files.model.RemoteFile; * Implementation of IFile for ownCloud servers. */ public class OwnCloudFile implements IFile { + final static String LOGTAG = "OwnCloudFile"; private OwnCloudProvider provider; private RemoteFile file; + /** We create the document just once, cache it for further returning. */ + private File mCachedFile; + private String name; private String parentPath; @@ -136,9 +141,12 @@ public class OwnCloudFile implements IFile { @Override public File getDocument() { - if (isDirectory()) { + if (mCachedFile != null) + return mCachedFile; + + if (isDirectory()) return null; - } + File downFolder = provider.getCacheDir(); DownloadFileRemoteOperation operation = new DownloadFileRemoteOperation( file.getRemotePath(), downFolder.getAbsolutePath()); @@ -146,7 +154,9 @@ public class OwnCloudFile implements IFile { if (!result.isSuccess()) { throw provider.buildRuntimeExceptionForResultCode(result.getCode()); } - return new File(downFolder.getAbsolutePath() + file.getRemotePath()); + + mCachedFile = new File(downFolder.getAbsolutePath() + file.getRemotePath()); + return mCachedFile; } @Override @@ -160,14 +170,19 @@ public class OwnCloudFile implements IFile { } @Override - public void saveDocument(File newFile) { + public void saveDocument() { + if (mCachedFile == null) { + Log.e(LOGTAG, "Trying to save document that was not created via getDocument()("); + return; + } + UploadFileRemoteOperation uploadOperation; - if (newFile.length() > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) { + if (mCachedFile.length() > ChunkedFileUploadRemoteOperation.CHUNK_SIZE_MOBILE) { uploadOperation = new ChunkedFileUploadRemoteOperation( - newFile.getPath(), file.getRemotePath(), file.getMimeType(), file.getEtag(), String.valueOf(file.getModifiedTimestamp()), false /* TODO actually check if on Wifi */); + mCachedFile.getPath(), file.getRemotePath(), file.getMimeType(), file.getEtag(), String.valueOf(mCachedFile.lastModified()), false /* TODO actually check if on Wifi */); } else { - uploadOperation = new UploadFileRemoteOperation(newFile.getPath(), - file.getRemotePath(), file.getMimeType(), String.valueOf(file.getModifiedTimestamp())); + uploadOperation = new UploadFileRemoteOperation(mCachedFile.getPath(), + file.getRemotePath(), file.getMimeType(), String.valueOf(mCachedFile.lastModified())); } RemoteOperationResult result = uploadOperation.execute(provider @@ -177,3 +192,5 @@ public class OwnCloudFile implements IFile { } } } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/android/app/src/main/java/org/libreoffice/androidapp/ui/LibreOfficeUIActivity.java b/android/app/src/main/java/org/libreoffice/androidapp/ui/LibreOfficeUIActivity.java index 037de08ad..c21e884f5 100644 --- a/android/app/src/main/java/org/libreoffice/androidapp/ui/LibreOfficeUIActivity.java +++ b/android/app/src/main/java/org/libreoffice/androidapp/ui/LibreOfficeUIActivity.java @@ -95,6 +95,7 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; import androidx.drawerlayout.widget.DrawerLayout; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -120,6 +121,9 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings private IFile currentDirectory; private int currentlySelectedFile; + /** The document that is being edited - to know what to save back to cloud. */ + private IFile mCurrentDocument; + private static final String CURRENT_DIRECTORY_KEY = "CURRENT_DIRECTORY"; private static final String DOC_PROVIDER_KEY = "CURRENT_DOCUMENT_PROVIDER"; private static final String FILTER_MODE_KEY = "FILTER_MODE"; @@ -164,6 +168,9 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings private LinearLayout impressLayout; private LinearLayout calcLayout; + /** Request code to evaluate that we are returning from the LOActivity. */ + private static final int LO_ACTIVITY_REQUEST_CODE = 42; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -182,9 +189,12 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); registerReceiver(mUSBReceiver, filter); + + // Register the LOActivity events broadcast receiver + LocalBroadcastManager.getInstance(this).registerReceiver(mLOActivityReceiver, + new IntentFilter(LOActivity.LO_ACTIVITY_BROADCAST)); + // init UI and populate with contents from the provider - - createUI(); fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open); fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close); @@ -540,6 +550,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings public void open(final IFile document) { addDocumentToRecents(document); + mCurrentDocument = document; new AsyncTask() { @Override protected File doInBackground(IFile... document) { @@ -566,8 +577,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings if (file != null) { Intent i = new Intent(Intent.ACTION_VIEW, Uri.fromFile(file)); String packageName = getApplicationContext().getPackageName(); - ComponentName componentName = new ComponentName(packageName, - LOActivity.class.getName()); + ComponentName componentName = new ComponentName(packageName, LOActivity.class.getName()); i.setComponent(componentName); // these extras allow to rebuild the IFile object in LOActivity @@ -576,7 +586,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings i.putExtra("org.libreoffice.document_uri", document.getUri()); - startActivity(i); + startActivityForResult(i, LO_ACTIVITY_REQUEST_CODE); } } }.execute(document); @@ -616,7 +626,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings i.putExtra("org.libreoffice.document_provider_id", documentProvider.getId()); i.putExtra("org.libreoffice.document_uri", newDocUri); - startActivity(i); + startActivityForResult(i, LO_ACTIVITY_REQUEST_CODE); } else { Toast.makeText(LibreOfficeUIActivity.this, getString(R.string.file_creation_failed), Toast.LENGTH_SHORT).show(); } @@ -1022,6 +1032,67 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings } }; + /** Receiver for receiving messages from LOActivity - like that Save was performed and similar. */ + private final BroadcastReceiver mLOActivityReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String event = intent.getStringExtra(LOActivity.LO_ACTION_EVENT); + Log.d(LOGTAG, "Received a message from LOActivity: " + event); + + // Handle various events from LOActivity + if (event.equals("SAVE")) { + LibreOfficeUIActivity.this.saveFileToCloud(); + } + } + }; + + /** Uploading back when we return from the LOActivity. */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == LO_ACTIVITY_REQUEST_CODE) { + Log.d(LOGTAG, "LOActivity has finished."); + saveFileToCloud(); + } + } + + /** Actually copy the file to the remote storage (if applicable). */ + public void saveFileToCloud() { + if (mCurrentDocument == null) + { + Log.e(LOGTAG, "No idea what should be uploaded."); + return; + } + + final AsyncTask task = new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + try { + // call document provider save operation + mCurrentDocument.saveDocument(); + } + catch (final RuntimeException e) { + LibreOfficeUIActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(LibreOfficeUIActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + Log.e(LOGTAG, e.getMessage(), e.getCause()); + } + return null; + } + + @Override + protected void onPostExecute(Void param) { + // FIXME Should we present a toast that the file was saved, or + // is it annoying? + //Toast.makeText(LibreOfficeUIActivity.this, R.string.message_saved, Toast.LENGTH_SHORT).show(); + } + }; + + task.execute(); + } + @Override protected void onPause() { super.onPause(); @@ -1061,6 +1132,7 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings protected void onDestroy() { super.onDestroy(); unregisterReceiver(mUSBReceiver); + unregisterReceiver(mLOActivityReceiver); Log.d(LOGTAG, "onDestroy"); } @@ -1082,7 +1154,6 @@ public class LibreOfficeUIActivity extends AppCompatActivity implements Settings //put the new value in the first place recentsArrayList.add(0, newRecent); - /* * 4 because the number of recommended items in App Shortcuts is 4, and also * because it's a good number of recent items in general diff --git a/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java b/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java index 3fe1c317f..66dc87497 100644 --- a/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java +++ b/android/lib/src/main/java/org/libreoffice/androidlib/LOActivity.java @@ -60,6 +60,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; public class LOActivity extends AppCompatActivity { final static String TAG = "LOActivity"; @@ -98,6 +99,15 @@ public class LOActivity extends AppCompatActivity { private ValueCallback valueCallback; public static final int REQUEST_SELECT_FILE = 555; + /** Broadcasting event for passing info back to the shell. */ + public static final String LO_ACTIVITY_BROADCAST = "LOActivityBroadcast"; + + /** Event description for passing info back to the shell. */ + public static final String LO_ACTION_EVENT = "LOEvent"; + + /** Data description for passing info back to the shell. */ + public static final String LO_ACTION_DATA = "LOData"; + private static boolean copyFromAssets(AssetManager assetManager, String fromAssetPath, String targetDir) { try { @@ -647,8 +657,13 @@ public class LOActivity extends AppCompatActivity { }); } - /** Could be overridden if it's necessary to forward some callbacks elsewhere. */ - public void sendBroadcast(String event, String data) {} + /** Send message back to the shell (for example for the cloud save). */ + public void sendBroadcast(String event, String data) { + Intent intent = new Intent(LO_ACTIVITY_BROADCAST); + intent.putExtra(LO_ACTION_EVENT, event); + intent.putExtra(LO_ACTION_DATA, data); + LocalBroadcastManager.getInstance(this).sendBroadcast(intent); + } public native void saveAs(String fileUri, String format);