nextcloud-android/src/main/java/com/owncloud/android/ui/activity/RichDocumentsWebView.java

424 lines
15 KiB
Java

/*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* @author Chris Narkiewicz
*
* Copyright (C) 2018 Tobias Kaminsky
* Copyright (C) 2018 Nextcloud GmbH.
* Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.activity;
import android.accounts.Account;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.client.account.CurrentAccountProvider;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.Template;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
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.operations.RichDocumentsUrlOperation;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
import org.parceler.Parcels;
import java.lang.ref.WeakReference;
import javax.inject.Inject;
import androidx.annotation.RequiresApi;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
/**
* Opens document for editing via Richdocuments app in a web view
*/
@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;
private Unbinder unbinder;
private OCFile file;
public ValueCallback<Uri[]> uploadMessage;
@BindView(R.id.progressBar2)
ProgressBar progressBar;
@BindView(R.id.thumbnail)
ImageView thumbnail;
@BindView(R.id.filename)
TextView fileName;
@Inject
protected CurrentAccountProvider currentAccountProvider;
@SuppressLint("AddJavascriptInterface") // suppress warning as webview is only used >= Lollipop
@Override
protected void onCreate(Bundle savedInstanceState) {
showToolbar = false;
webViewLayout = R.layout.richdocuments_webview;
super.onCreate(savedInstanceState);
unbinder = ButterKnife.bind(this);
file = getIntent().getParcelableExtra(EXTRA_FILE);
// TODO make file nullable
if (file == null) {
fileName.setText(R.string.create_file_from_template);
Template template = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_TEMPLATE));
int placeholder;
switch (template.getType()) {
case "document":
placeholder = R.drawable.file_doc;
break;
case "spreadsheet":
placeholder = R.drawable.file_xls;
break;
case "presentation":
placeholder = R.drawable.file_ppt;
break;
default:
placeholder = R.drawable.file;
break;
}
Glide.with(this).using(new CustomGlideStreamLoader(currentAccountProvider)).load(template.getThumbnailLink())
.placeholder(placeholder)
.error(placeholder)
.into(thumbnail);
} else {
setThumbnail(file, thumbnail);
fileName.setText(file.getFileName());
}
webview.addJavascriptInterface(new RichDocumentsMobileInterface(), "RichDocumentsMobileInterface");
webview.setWebChromeClient(new WebChromeClient() {
RichDocumentsWebView activity = RichDocumentsWebView.this;
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
if (uploadMessage != null) {
uploadMessage.onReceiveValue(null);
uploadMessage = null;
}
activity.uploadMessage = filePathCallback;
Intent intent = fileChooserParams.createIntent();
intent.setType("image/*");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
try {
activity.startActivityForResult(intent, REQUEST_LOCAL_FILE);
} catch (ActivityNotFoundException e) {
uploadMessage = null;
Toast.makeText(getBaseContext(), "Cannot open file chooser", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
});
// load url in background
url = getIntent().getStringExtra(EXTRA_URL);
if (TextUtils.isEmpty(url)) {
new LoadUrl(this, getAccount()).execute(file.getLocalId());
} else {
webview.loadUrl(url);
}
}
private void setThumbnail(OCFile file, ImageView thumbnailView) {
// Todo minimize: only icon by mimetype
if (file.isFolder()) {
thumbnailView.setImageDrawable(MimeTypeUtil.getFolderTypeIcon(file.isSharedWithMe() ||
file.isSharedWithSharee(), file.isSharedViaLink(), file.isEncrypted(), file.getMountType(),
this));
} else {
if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file)) && file.getRemoteId() != null) {
// Thumbnail in cache?
Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId());
if (thumbnail != null && !file.isUpdateThumbnailNeeded()) {
if (MimeTypeUtil.isVideo(file)) {
Bitmap withOverlay = ThumbnailsCacheManager.addVideoOverlay(thumbnail);
thumbnailView.setImageBitmap(withOverlay);
} else {
thumbnailView.setImageBitmap(thumbnail);
}
} else {
// generate new thumbnail
if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailView)) {
try {
final ThumbnailsCacheManager.ThumbnailGenerationTask task =
new ThumbnailsCacheManager.ThumbnailGenerationTask(thumbnailView,
getStorageManager(), getAccount());
if (thumbnail == null) {
if (MimeTypeUtil.isVideo(file)) {
thumbnail = ThumbnailsCacheManager.mDefaultVideo;
} else {
thumbnail = ThumbnailsCacheManager.mDefaultImg;
}
}
final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable =
new ThumbnailsCacheManager.AsyncThumbnailDrawable(getResources(), thumbnail, task);
thumbnailView.setImageDrawable(asyncDrawable);
task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file,
file.getRemoteId()));
} catch (IllegalArgumentException e) {
Log_OC.d(TAG, "ThumbnailGenerationTask : " + e.getMessage());
}
}
}
if ("image/png".equalsIgnoreCase(file.getMimeType())) {
thumbnailView.setBackgroundColor(getResources().getColor(R.color.background_color));
}
} else {
thumbnailView.setImageDrawable(MimeTypeUtil.getFileTypeIcon(file.getMimeType(), file.getFileName(),
getAccount(), this));
}
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
private void openFileChooser() {
Intent action = new Intent(this, FilePickerActivity.class);
action.putExtra(OCFileListFragment.ARG_MIMETYPE, "image/");
startActivityForResult(action, REQUEST_REMOTE_FILE);
}
private void openShareDialog() {
Intent intent = new Intent(this, ShareActivity.class);
intent.putExtra(FileActivity.EXTRA_FILE, file);
intent.putExtra(FileActivity.EXTRA_ACCOUNT, getAccount());
startActivity(intent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (RESULT_OK != resultCode) {
// TODO
return;
}
switch (requestCode) {
case REQUEST_LOCAL_FILE:
handleLocalFile(data, resultCode);
break;
case REQUEST_REMOTE_FILE:
handleRemoteFile(data);
break;
default:
// unexpected, do nothing
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
private void handleLocalFile(Intent data, int resultCode) {
if (uploadMessage == null) {
return;
}
uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
uploadMessage = null;
}
private void handleRemoteFile(Intent data) {
OCFile file = data.getParcelableExtra(FolderPickerActivity.EXTRA_FILES);
new Thread(() -> {
Account account = AccountUtils.getCurrentOwnCloudAccount(this);
RichDocumentsCreateAssetOperation operation = new RichDocumentsCreateAssetOperation(file.getRemotePath());
RemoteOperationResult result = operation.execute(account, this);
if (result.isSuccess()) {
String asset = (String) result.getSingleData();
runOnUiThread(() -> webview.evaluateJavascript("OCA.RichDocuments.documentsMain.postAsset('" +
file.getFileName() + "', '" + asset + "');", null));
} else {
runOnUiThread(() -> DisplayUtils.showSnackMessage(this, "Inserting image failed!"));
}
}).start();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString(EXTRA_URL, url);
super.onSaveInstanceState(outState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
url = savedInstanceState.getString(EXTRA_URL);
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onDestroy() {
unbinder.unbind();
webview.destroy();
super.onDestroy();
}
private void closeView() {
webview.destroy();
finish();
}
private void hideLoading() {
thumbnail.setVisibility(View.GONE);
fileName.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
webview.setVisibility(View.VISIBLE);
}
private class RichDocumentsMobileInterface {
@JavascriptInterface
public void close() {
runOnUiThread(RichDocumentsWebView.this::closeView);
}
@JavascriptInterface
public void insertGraphic() {
openFileChooser();
}
@JavascriptInterface
public void share() {
openShareDialog();
}
@JavascriptInterface
public void documentLoaded() {
runOnUiThread(RichDocumentsWebView.this::hideLoading);
}
}
private static class LoadUrl extends AsyncTask<String, Void, String> {
private Account account;
private WeakReference<RichDocumentsWebView> richDocumentsWebViewWeakReference;
LoadUrl(RichDocumentsWebView richDocumentsWebView, Account account) {
this.account = account;
this.richDocumentsWebViewWeakReference = new WeakReference<>(richDocumentsWebView);
}
@Override
protected String doInBackground(String... fileId) {
if (richDocumentsWebViewWeakReference.get() == null) {
return "";
}
RichDocumentsUrlOperation richDocumentsUrlOperation = new RichDocumentsUrlOperation(fileId[0]);
RemoteOperationResult result = richDocumentsUrlOperation.execute(account,
richDocumentsWebViewWeakReference.get());
if (!result.isSuccess()) {
return "";
}
return (String) result.getData().get(0);
}
@Override
protected void onPostExecute(String url) {
RichDocumentsWebView richDocumentsWebView = richDocumentsWebViewWeakReference.get();
if (richDocumentsWebView == null) {
return;
}
if (!url.isEmpty()) {
richDocumentsWebView.webview.loadUrl(url);
new Handler().postDelayed(() -> {
if (richDocumentsWebView.webview.getVisibility() != View.VISIBLE) {
DisplayUtils.createSnackbar(richDocumentsWebView.findViewById(android.R.id.content),
R.string.timeout_richDocuments, Snackbar.LENGTH_INDEFINITE)
.setActionTextColor(richDocumentsWebView.getResources().getColor(R.color.white))
.setAction(R.string.fallback_weblogin_back, v -> richDocumentsWebView.closeView()).show();
}
}, 10 * 1000);
} else {
Toast.makeText(richDocumentsWebView.getApplicationContext(),
R.string.richdocuments_failed_to_load_document, Toast.LENGTH_LONG).show();
richDocumentsWebView.finish();
}
}
}
}