diff --git a/scripts/analysis/findbugs-results.txt b/scripts/analysis/findbugs-results.txt
index 8e9088e93b..9f51d082f8 100644
--- a/scripts/analysis/findbugs-results.txt
+++ b/scripts/analysis/findbugs-results.txt
@@ -1 +1 @@
-411
\ No newline at end of file
+426
diff --git a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java
index af07a4cbe4..f08055305a 100644
--- a/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java
+++ b/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java
@@ -49,16 +49,20 @@ import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.client.account.CurrentAccountProvider;
+import com.nextcloud.client.network.ClientFactory;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.Template;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.RichDocumentsCreateAssetOperation;
import com.owncloud.android.ui.asynctasks.LoadUrlTask;
+import com.owncloud.android.ui.asynctasks.PrintAsyncTask;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
@@ -66,6 +70,9 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.parceler.Parcels;
+import java.io.File;
+import java.lang.ref.WeakReference;
+
import javax.inject.Inject;
import androidx.annotation.RequiresApi;
@@ -81,12 +88,14 @@ import lombok.Setter;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class RichDocumentsWebView extends ExternalSiteWebView {
- private static final String TAG = RichDocumentsWebView.class.getSimpleName();
- private static final int REQUEST_REMOTE_FILE = 100;
-
- public static final int REQUEST_LOCAL_FILE = 101;
-
public static final int MINIMUM_API = Build.VERSION_CODES.LOLLIPOP;
+ public static final int REQUEST_LOCAL_FILE = 101;
+ private static final int REQUEST_REMOTE_FILE = 100;
+ private static final String TAG = RichDocumentsWebView.class.getSimpleName();
+ private static final String URL = "URL";
+ private static final String TYPE = "Type";
+ private static final String PRINT = "print";
+ private static final String NEW_NAME = "NewName";
private Unbinder unbinder;
private OCFile file;
@@ -106,6 +115,9 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
@Inject
protected CurrentAccountProvider currentAccountProvider;
+ @Inject
+ protected ClientFactory clientFactory;
+
@SuppressLint("AddJavascriptInterface") // suppress warning as webview is only used >= Lollipop
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -196,8 +208,8 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
if (file.isFolder()) {
thumbnailView.setImageDrawable(MimeTypeUtil.getFolderTypeIcon(file.isSharedWithMe() ||
- file.isSharedWithSharee(), file.isSharedViaLink(), file.isEncrypted(), file.getMountType(),
- this));
+ file.isSharedWithSharee(), file.isSharedViaLink(), file.isEncrypted(), file.getMountType(),
+ this));
} else {
if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) {
// Thumbnail in cache?
@@ -217,7 +229,7 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
try {
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView,
- getStorageManager(), getAccount());
+ getStorageManager(), getAccount());
if (thumbnail == null) {
if (MimeTypeUtil.isVideo(file)) {
@@ -230,7 +242,7 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
new ThumbnailsCacheManager.AsyncThumbnailDrawable(getResources(), thumbnail, task);
thumbnailView.setImageDrawable(asyncDrawable);
task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file,
- file.getRemoteId()));
+ file.getRemoteId()));
} catch (IllegalArgumentException e) {
Log_OC.d(TAG, "ThumbnailGenerationTask : " + e.getMessage());
}
@@ -242,7 +254,7 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
}
} else {
thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(),
- getAccount(), this));
+ getAccount(), this));
}
}
}
@@ -310,7 +322,7 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
String asset = (String) result.getSingleData();
runOnUiThread(() -> webview.evaluateJavascript("OCA.RichDocuments.documentsMain.postAsset('" +
- file.getFileName() + "', '" + asset + "');", null));
+ file.getFileName() + "', '" + asset + "');", null));
} else {
runOnUiThread(() -> DisplayUtils.showSnackMessage(this, "Inserting image failed!"));
}
@@ -361,6 +373,34 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
"{ OCA.RichDocuments.documentsMain.postGrabFocus(); }", null);
}
+ private void printFile(Uri url) {
+ OwnCloudAccount account = accountManager.getCurrentOwnCloudAccount();
+
+ if (account == null) {
+ DisplayUtils.showSnackMessage(webview, getString(R.string.failed_to_print));
+ return;
+ }
+
+ File targetFile = new File(FileStorageUtils.getTemporalPath(account.getName()) + "/print.pdf");
+
+ new PrintAsyncTask(targetFile, url.toString(), new WeakReference<>(this)).execute();
+ }
+
+ private void downloadFile(Uri url) {
+ DownloadManager downloadmanager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
+
+ if (downloadmanager == null) {
+ DisplayUtils.showSnackMessage(webview, getString(R.string.failed_to_download));
+ return;
+ }
+
+ DownloadManager.Request request = new DownloadManager.Request(url);
+ request.allowScanningByMediaScanner();
+ request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+
+ downloadmanager.enqueue(request);
+ }
+
private class RichDocumentsMobileInterface {
@JavascriptInterface
public void close() {
@@ -384,27 +424,22 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
@JavascriptInterface
public void downloadAs(String json) {
- Uri downloadUrl;
try {
JSONObject downloadJson = new JSONObject(json);
- downloadUrl = Uri.parse(downloadJson.getString("URL"));
+
+ Uri url = Uri.parse(downloadJson.getString(URL));
+
+ if (downloadJson.getString(TYPE).equalsIgnoreCase(PRINT)) {
+ printFile(url);
+ } else {
+ downloadFile(url);
+ }
} catch (JSONException e) {
- Log_OC.e(this, "Failed to parse rename json message: " + e);
+ Log_OC.e(this, "Failed to parse download json message: " + e);
return;
}
- DownloadManager downloadmanager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
- if (downloadmanager == null) {
- DisplayUtils.showSnackMessage(webview, getString(R.string.failed_to_download));
- return;
- }
-
- DownloadManager.Request request = new DownloadManager.Request(downloadUrl);
- request.allowScanningByMediaScanner();
- request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
-
- downloadmanager.enqueue(request);
}
@JavascriptInterface
@@ -413,7 +448,7 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
// need to change filename for sharing
try {
JSONObject renameJson = new JSONObject(renameString);
- String newName = renameJson.getString("NewName");
+ String newName = renameJson.getString(NEW_NAME);
file.setFileName(newName);
} catch (JSONException e) {
Log_OC.e(this, "Failed to parse rename json message: " + e);
@@ -422,7 +457,7 @@ public class RichDocumentsWebView extends ExternalSiteWebView {
@JavascriptInterface
public void paste() {
- // Javascript cannot do this by itself, so help out.
+ // Javascript cannot do this by itself, so help out.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
webview.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PASTE));
webview.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_PASTE));
diff --git a/src/main/java/com/owncloud/android/ui/adapter/PrintAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/PrintAdapter.java
new file mode 100644
index 0000000000..3fe87eb98a
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/adapter/PrintAdapter.java
@@ -0,0 +1,110 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2019 Tobias Kaminsky
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.adapter;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentInfo;
+
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Objects;
+
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class PrintAdapter extends PrintDocumentAdapter {
+ private static final String TAG = PrintAdapter.class.getSimpleName();
+ private static final String PDF_NAME = "finalPrint.pdf";
+
+ private String filePath;
+
+ public PrintAdapter(String filePath) {
+ this.filePath = filePath;
+ }
+
+ @Override
+ public void onLayout(PrintAttributes oldAttributes,
+ PrintAttributes newAttributes,
+ CancellationSignal cancellationSignal,
+ LayoutResultCallback callback,
+ Bundle extras) {
+ if (cancellationSignal.isCanceled()) {
+ callback.onLayoutCancelled();
+ } else {
+ PrintDocumentInfo.Builder builder = new PrintDocumentInfo.Builder(PDF_NAME);
+ builder.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN)
+ .build();
+ callback.onLayoutFinished(builder.build(), !newAttributes.equals(oldAttributes));
+ }
+ }
+
+
+ @Override
+ public void onWrite(PageRange[] pages,
+ ParcelFileDescriptor destination,
+ CancellationSignal cancellationSignal,
+ WriteResultCallback callback) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = new FileInputStream(new File(filePath));
+ out = new FileOutputStream(destination.getFileDescriptor());
+
+ byte[] buf = new byte[16384];
+ int size;
+
+ while ((size = in.read(buf)) >= 0 && !cancellationSignal.isCanceled()) {
+ out.write(buf, 0, size);
+ }
+
+ if (cancellationSignal.isCanceled()) {
+ callback.onWriteCancelled();
+ } else {
+ callback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
+ }
+
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Error using temp file", e);
+ } finally {
+ try {
+ Objects.requireNonNull(in).close();
+ Objects.requireNonNull(out).close();
+
+ } catch (IOException | NullPointerException e) {
+ Log_OC.e(TAG, "Error closing streams", e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/owncloud/android/ui/asynctasks/PrintAsyncTask.java b/src/main/java/com/owncloud/android/ui/asynctasks/PrintAsyncTask.java
new file mode 100644
index 0000000000..5b8ebbad1a
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/asynctasks/PrintAsyncTask.java
@@ -0,0 +1,150 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2019 Tobias Kaminsky
+ * Copyright (C) 2019 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.asynctasks;
+
+import android.os.AsyncTask;
+import android.os.Build;
+import android.print.PrintAttributes;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintManager;
+
+import com.owncloud.android.R;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.activity.RichDocumentsWebView;
+import com.owncloud.android.ui.adapter.PrintAdapter;
+import com.owncloud.android.utils.DisplayUtils;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+
+import androidx.annotation.RequiresApi;
+
+import static android.content.Context.PRINT_SERVICE;
+
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class PrintAsyncTask extends AsyncTask {
+ private static final String TAG = PrintAsyncTask.class.getSimpleName();
+ private static final String JOB_NAME = "Document";
+
+ private File file;
+ private String url;
+ private WeakReference richDocumentsWebViewWeakReference;
+
+ public PrintAsyncTask(File file, String url, WeakReference richDocumentsWebViewWeakReference) {
+ this.file = file;
+ this.url = url;
+ this.richDocumentsWebViewWeakReference = richDocumentsWebViewWeakReference;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ richDocumentsWebViewWeakReference.get().runOnUiThread(
+ () -> richDocumentsWebViewWeakReference.get().showLoadingDialog(
+ richDocumentsWebViewWeakReference.get().getString(R.string.common_loading)));
+
+ super.onPreExecute();
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ HttpClient client = new HttpClient();
+ GetMethod getMethod = new GetMethod(url);
+
+ FileOutputStream fos;
+ try {
+ int status = client.executeMethod(getMethod);
+ if (status == HttpStatus.SC_OK) {
+ if (file.exists()) {
+ if (!file.delete()) {
+ return false;
+ }
+ }
+
+ file.getParentFile().mkdirs();
+
+ if (!file.getParentFile().exists()) {
+ Log_OC.d(TAG, file.getParentFile().getAbsolutePath() + " does not exist");
+ return false;
+ }
+
+ if (!file.createNewFile()) {
+ Log_OC.d(TAG, file.getAbsolutePath() + " could not be created");
+ return false;
+ }
+
+ BufferedInputStream bis = new BufferedInputStream(getMethod.getResponseBodyAsStream());
+ fos = new FileOutputStream(file);
+ long transferred = 0;
+
+ Header contentLength = getMethod.getResponseHeader("Content-Length");
+ long totalToTransfer = contentLength != null && contentLength.getValue().length() > 0 ?
+ Long.parseLong(contentLength.getValue()) : 0;
+
+ byte[] bytes = new byte[4096];
+ int readResult;
+ while ((readResult = bis.read(bytes)) != -1) {
+ fos.write(bytes, 0, readResult);
+ transferred += readResult;
+ }
+ // Check if the file is completed
+ if (transferred != totalToTransfer) {
+ return false;
+ }
+
+ if (getMethod.getResponseBodyAsStream() != null) {
+ getMethod.getResponseBodyAsStream().close();
+ }
+ }
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Error reading file", e);
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ RichDocumentsWebView richDocumentsWebView = richDocumentsWebViewWeakReference.get();
+ richDocumentsWebView.dismissLoadingDialog();
+
+ PrintManager printManager = (PrintManager) richDocumentsWebView.getSystemService(PRINT_SERVICE);
+
+ if (!result || printManager == null) {
+ DisplayUtils.showSnackMessage(richDocumentsWebView,
+ richDocumentsWebView.getString(R.string.failed_to_print));
+
+ return;
+ }
+
+ PrintDocumentAdapter printAdapter = new PrintAdapter(file.getAbsolutePath());
+
+ printManager.print(JOB_NAME, printAdapter, new PrintAttributes.Builder().build());
+ }
+}
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index dc20f454e9..504ecc58ec 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -884,6 +884,7 @@
Copy internal link
Only works for users with access to this folder
Failed to pass file to download manager
+ Failed to print file
Disable power save check
Disabling power save check might result in uploading files when in low battery state!
Engineering Test Mode