Add printing system to RichDocuments (#4365)

Add printing system to RichDocuments
This commit is contained in:
Tobias Kaminsky 2019-08-27 08:49:18 +02:00 committed by GitHub
commit 575be806e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 324 additions and 28 deletions

View file

@ -1 +1 @@
411
426

View file

@ -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));

View file

@ -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);
}
}
}
}

View file

@ -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());
}
}

View file

@ -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>