Implement WebXDC importFiles

Stephen Paul Weber created

Change summary

src/cheogram/java/com/cheogram/android/WebxdcPage.java              | 23 
src/cheogram/res/raw/webxdc.js                                      | 21 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java   |  2 
src/main/java/eu/siacs/conversations/ui/XmppActivity.java           | 26 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java |  2 
5 files changed, 70 insertions(+), 4 deletions(-)

Detailed changes

src/cheogram/java/com/cheogram/android/WebxdcPage.java 🔗

@@ -18,13 +18,15 @@ import android.view.LayoutInflater;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
 import android.webkit.JavascriptInterface;
+import android.webkit.ValueCallback;
+import android.webkit.WebChromeClient;
 import android.webkit.WebResourceRequest;
 import android.webkit.WebResourceResponse;
 import android.webkit.WebSettings;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
+import android.widget.ArrayAdapter;
 import android.widget.TextView;
 
 import androidx.annotation.RequiresApi;
@@ -64,6 +66,7 @@ import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.ConversationsActivity;
+import eu.siacs.conversations.ui.XmppActivity;
 import eu.siacs.conversations.utils.MimeUtils;
 import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xml.Element;
@@ -77,10 +80,12 @@ public class WebxdcPage implements ConversationPage {
 	protected String baseUrl;
 	protected Message source;
 	protected WebxdcUpdate lastUpdate = null;
+	protected WeakReference<XmppActivity> activity;
 
-	public WebxdcPage(Cid cid, Message source, XmppConnectionService xmppConnectionService) {
+	public WebxdcPage(final XmppActivity activity, Cid cid, Message source, XmppConnectionService xmppConnectionService) {
 		this.xmppConnectionService = xmppConnectionService;
 		this.source = source;
+		this.activity = new WeakReference(activity);
 		File f = xmppConnectionService.getFileForCid(cid);
 		try {
 			if (f != null) zip = new ZipFile(xmppConnectionService.getFileForCid(cid));
@@ -252,6 +257,20 @@ public class WebxdcPage implements ConversationPage {
 			}
 		});
 
+		binding.webview.setWebChromeClient(new WebChromeClient() {
+			@Override
+			public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
+				// WebxdcActivity.this.filePathCallback = filePathCallback;
+				Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+				intent.addCategory(Intent.CATEGORY_OPENABLE);
+				intent.setType("*/*");
+				intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
+				final XmppActivity activity = WebxdcPage.this.activity.get();
+				if (activity != null) activity.startActivityWithCallback(Intent.createChooser(intent, "Choose a file"), filePathCallback);
+				return activity != null;
+			}
+		});
+
 		// disable "safe browsing" as this has privacy issues,
 		// eg. at least false positives are sent to the "Safe Browsing Lookup API".
 		// as all URLs opened in the WebView are local anyway,

src/cheogram/res/raw/webxdc.js 🔗

@@ -36,5 +36,26 @@ window.webxdc = (() => {
 		sendUpdate: (payload, descr) => {
 			InternalJSApi.sendStatusUpdate(JSON.stringify(payload), descr);
 		},
+
+		importFiles: (filters) => {
+			var element = document.createElement("input");
+			element.type = "file";
+			element.accept = [
+					...(filters.extensions || []),
+					...(filters.mimeTypes || []),
+			].join(",");
+			element.multiple = filters.multiple || false;
+			const promise = new Promise((resolve, _reject) => {
+					element.onchange = (_ev) => {
+							const files = Array.from(element.files || []);
+							document.body.removeChild(element);
+							resolve(files);
+					};
+			});
+			element.style.display = "none";
+			document.body.appendChild(element);
+			element.click();
+			return promise;
+		},
 	};
 })();

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -3213,7 +3213,7 @@ public class ConversationFragment extends XmppFragment
             if (message == null) return;
 
             Cid webxdcCid = message.getFileParams().getCids().get(0);
-            WebxdcPage webxdc = new WebxdcPage(webxdcCid, message, activity.xmppConnectionService);
+            WebxdcPage webxdc = new WebxdcPage(activity, webxdcCid, message, activity.xmppConnectionService);
             Conversation conversation = (Conversation) message.getConversation();
             if (!conversation.switchToSession("webxdc\0" + message.getUuid())) {
                 conversation.startWebxdc(webxdc);

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -41,9 +41,11 @@ import android.text.Html;
 import android.text.InputType;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Pair;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.webkit.ValueCallback;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.ImageView;
@@ -65,6 +67,7 @@ import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.PriorityQueue;
 import java.util.concurrent.RejectedExecutionException;
 
 import eu.siacs.conversations.Config;
@@ -117,6 +120,7 @@ public abstract class XmppActivity extends ActionBarActivity {
     protected Toast mToast;
     public Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show();
     protected ConferenceInvite mPendingConferenceInvite = null;
+    protected PriorityQueue<Pair<Integer, ValueCallback<Uri[]>>> activityCallbacks = new PriorityQueue<>((x, y) -> y.first.compareTo(x.first));
     protected ServiceConnection mConnection = new ServiceConnection() {
 
         @Override
@@ -854,6 +858,13 @@ public abstract class XmppActivity extends ActionBarActivity {
         }
     }
 
+    public synchronized void startActivityWithCallback(Intent intent, ValueCallback<Uri[]> cb) {
+        Pair<Integer, ValueCallback<Uri[]>> peek = activityCallbacks.peek();
+        int index = peek == null ? 1 : peek.first + 1;
+        activityCallbacks.add(new Pair<>(index, cb));
+        startActivityForResult(intent, index);
+    }
+
     protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
@@ -865,6 +876,21 @@ public abstract class XmppActivity extends ActionBarActivity {
                 }
                 mPendingConferenceInvite = null;
             }
+        } else if (resultCode == RESULT_OK) {
+            for (Pair<Integer, ValueCallback<Uri[]>> cb : new ArrayList<>(activityCallbacks)) {
+                if (cb.first == requestCode) {
+                    activityCallbacks.remove(cb);
+                    ArrayList<Uri> dataUris = new ArrayList<>();
+                    if (data.getDataString() != null) {
+                        dataUris.add(Uri.parse(data.getDataString()));
+                    } else if (data.getClipData() != null) {
+                        for (int i = 0; i < data.getClipData().getItemCount(); i++) {
+                            dataUris.add(data.getClipData().getItemAt(i).getUri());
+                        }
+                    }
+                    cb.second.onReceiveValue(dataUris.toArray(new Uri[0]));
+                }
+            }
         }
     }
 

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -689,7 +689,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
 
     private void displayWebxdcMessage(ViewHolder viewHolder, final Message message, final boolean darkBackground, final int type) {
         Cid webxdcCid = message.getFileParams().getCids().get(0);
-        WebxdcPage webxdc = new WebxdcPage(webxdcCid, message, activity.xmppConnectionService);
+        WebxdcPage webxdc = new WebxdcPage(activity, webxdcCid, message, activity.xmppConnectionService);
         displayTextMessage(viewHolder, message, darkBackground, type);
         viewHolder.image.setVisibility(View.GONE);
         viewHolder.audioPlayer.setVisibility(View.GONE);