Эх сурвалжийг харах

Add printing system to RichDocuments

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 5 жил өмнө
parent
commit
d423dd3b00

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-411
+426

+ 61 - 26
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"));
-            } catch (JSONException e) {
-                Log_OC.e(this, "Failed to parse rename json message: " + e);
-                return;
-            }
 
-            DownloadManager downloadmanager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
+                Uri url = Uri.parse(downloadJson.getString(URL));
 
-            if (downloadmanager == null) {
-                DisplayUtils.showSnackMessage(webview, getString(R.string.failed_to_download));
+                if (downloadJson.getString(TYPE).equalsIgnoreCase(PRINT)) {
+                    printFile(url);
+                } else {
+                    downloadFile(url);
+                }
+            } catch (JSONException e) {
+                Log_OC.e(this, "Failed to parse download json message: " + e);
                 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));

+ 110 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+
+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);
+            }
+        }
+    }
+}

+ 150 - 0
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 <https://www.gnu.org/licenses/>.
+ */
+
+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<Void, Void, Boolean> {
+    private static final String TAG = PrintAsyncTask.class.getSimpleName();
+    private static final String JOB_NAME = "Document";
+
+    private File file;
+    private String url;
+    private WeakReference<RichDocumentsWebView> richDocumentsWebViewWeakReference;
+
+    public PrintAsyncTask(File file, String url, WeakReference<RichDocumentsWebView> 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());
+    }
+}

+ 1 - 0
src/main/res/values/strings.xml

@@ -884,6 +884,7 @@
     <string name="copy_internal_link">Copy internal link</string>
     <string name="copy_internal_link_subline">Only works for users with access to this folder</string>
     <string name="failed_to_download">Failed to pass file to download manager</string>
+    <string name="failed_to_print">Failed to print file</string>
     <string name="autoupload_disable_power_save_check">Disable power save check</string>
     <string name="power_save_check_dialog_message">Disabling power save check might result in uploading files when in low battery state!</string>
     <string name="etm_title">Engineering Test Mode</string>