[Android] Initial upload support, Context menu for links, More config options

This commit is contained in:
2025-03-18 23:09:21 +01:00
parent 8b47ef18b0
commit 4330c971d1
15 changed files with 161 additions and 59 deletions

View File

@ -27,3 +27,9 @@ android {
compileSdk 34
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

View File

@ -2,7 +2,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
android:installLocation="auto"
tools:ignore="UnusedAttribute">
<!-- Lets the app access the Internet — not needed for fully offline apps -->
<!-- <uses-permission android:name="android.permission.INTERNET" /> -->

View File

@ -27,7 +27,7 @@
<li><a href="intent://#Intent;action=android.intent.action.VIEW;scheme=http;type=video/mp4;end">intent://</a></li>
</ul>
<h3>Files</h3>
<p>Upload: <label><input type="file"/></label></p>
<p>Upload: <label><input type="file" onchange="(function(f){ alert(f.name + ' ' + f.size + ' ' + f.type); })(this.files[0]);" /></label></p>
<p>Download:</p>
<ul>
<li><a download="Hello World.txt" href="data:text/plain;utf8,Hello World!">data:, .txt</a></li>

View File

@ -1,5 +1,7 @@
package org.eu.spacc.spaccdotweb.android;
import android.webkit.WebSettings;
import org.eu.spacc.spaccdotweb.android.Constants.*;
import org.eu.spacc.spaccdotweb.android.helpers.ConfigReader;
@ -22,6 +24,16 @@ public class Config extends Defaults {
return (value != null ? value : Defaults.ALLOW_STORAGE);
}
public Boolean getAllowZoomControls() {
Boolean value = getBoolean("allow_zoom_controls");
return (value != null ? value : Defaults.ALLOW_ZOOM_CONTROLS);
}
public Boolean getDisplayZoomControls() {
Boolean value = getBoolean("display_zoom_controls");
return (value != null ? value : Defaults.DISPLAY_ZOOM_CONTROLS);
}
public AppIndex getAppIndex() {
AppIndex value = (AppIndex)get("app_index");
return (value != null ? value : Defaults.APP_INDEX);
@ -36,12 +48,18 @@ public class Config extends Defaults {
return getString("remote_index");
}
private Object get(String key) {
if (configReader != null) {
return configReader.get(key);
} else {
return null;
public String getStandardFontFamily() {
return getString("standard_font_family");
}
public String getUserAgent() {
return getString("user_agent");
}
private Object get(String key) {
return (configReader != null
? configReader.get(key)
: null);
}
private Boolean getBoolean(String key) {

View File

@ -1,7 +1,7 @@
package org.eu.spacc.spaccdotweb.android;
public class Constants {
//public enum ActivityCodes { DOWNLOAD_FILE, UPLOAD_FILE }
public enum ActivityCodes { UPLOAD_FILE /* , DOWNLOAD_FILE */ }
public enum AppIndex { LOCAL, REMOTE }
public enum DataLocation { INTERNAL, EXTERNAL }
}

View File

@ -5,6 +5,8 @@ import org.eu.spacc.spaccdotweb.android.Constants.*;
public class Defaults {
public static final Boolean ALLOW_JAVASCRIPT = true;
public static final Boolean ALLOW_STORAGE = true;
public static final Boolean ALLOW_ZOOM_CONTROLS = false;
public static final Boolean DISPLAY_ZOOM_CONTROLS = false;
public static final AppIndex APP_INDEX = AppIndex.LOCAL;
public static final String LOCAL_INDEX = "index.html";
}

View File

@ -1,23 +1,49 @@
package org.eu.spacc.spaccdotweb.android;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.webkit.ValueCallback;
import org.eu.spacc.spaccdotweb.android.webview.SpaccWebChromeClient;
import org.eu.spacc.spaccdotweb.android.webview.SpaccWebView;
import java.io.File;
public class SpaccWebViewActivity extends Activity {
protected SpaccWebView webView;
public ValueCallback<Uri> fileUploadCallback;
public ValueCallback<Uri[]> filesUploadCallback;
// @Override
// protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// super.onActivityResult(requestCode, resultCode, data);
@TargetApi(Build.VERSION_CODES.ECLAIR_MR1)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// if (requestCode == Constants.CREATE_FILE_REQUEST_CODE && resultCode == RESULT_OK && data != null) {
// Uri fileUri = data.getData();
// if (fileUri != null) {
// enqueueDownload(Uri.parse(fileUri.toString()));
// }
// }
// }
if (requestCode == Constants.ActivityCodes.UPLOAD_FILE.ordinal()) {
if (resultCode == Activity.RESULT_OK && data != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && filesUploadCallback != null) {
filesUploadCallback.onReceiveValue(SpaccWebChromeClient.FileChooserParams.parseResult(resultCode, data));
} else if (fileUploadCallback != null) {
fileUploadCallback.onReceiveValue(data.getData());
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && filesUploadCallback != null) {
filesUploadCallback.onReceiveValue(null);
} else if (fileUploadCallback != null) {
fileUploadCallback.onReceiveValue(null);
}
}
fileUploadCallback = null;
filesUploadCallback = null;
}
}
@Override
public void onBackPressed() {

View File

@ -23,4 +23,15 @@ public class ApiUtils {
context.startActivity(new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, url.toString()));
}
}
public static void writeToClipboard(Context context, String text) {
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
} else {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(null, text);
clipboard.setPrimaryClip(clip);
}
}
}

View File

@ -0,0 +1,40 @@
package org.eu.spacc.spaccdotweb.android.webview;
import android.annotation.TargetApi;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import org.eu.spacc.spaccdotweb.android.Constants;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
public class SpaccWebChromeClient extends WebChromeClient {
private final SpaccWebViewActivity activity;
public SpaccWebChromeClient(SpaccWebViewActivity activity) {
super();
this.activity = activity;
}
// TODO: Android < 4 support
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) {
activity.filesUploadCallback = valueCallback;
activity.startActivityForResult(fileChooserParams.createIntent(), Constants.ActivityCodes.UPLOAD_FILE.ordinal());
return true;
}
//@Override // Android 4.1+
protected void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
activity.fileUploadCallback = valueCallback;
activity.startActivityForResult(Intent.createChooser(intent, null), Constants.ActivityCodes.UPLOAD_FILE.ordinal());
}
}

View File

@ -1,32 +1,51 @@
package org.eu.spacc.spaccdotweb.android.webview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.ContextMenu;
import android.webkit.WebSettings;
import android.webkit.WebView;
import org.eu.spacc.spaccdotweb.android.Config;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
import org.eu.spacc.spaccdotweb.android.helpers.ConfigReader;
import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
public class SpaccWebView extends WebView {
private Config config;
private Context context;
@SuppressLint("SetJavaScriptEnabled")
public SpaccWebView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setWebViewClient(new WebViewClient(context));
this.setWebChromeClient(new WebChromeClient(context));
this.context = context;
this.setWebViewClient(new SpaccWebViewClient(context));
this.setWebChromeClient(new SpaccWebChromeClient((SpaccWebViewActivity)context));
this.setDownloadListener(new DownloadListener(context));
this.config = new Config();
this.applyConfig(context);
}
// TODO: Implement context menu (long-press on links, images, etc...)
// @Override
// protected void onCreateContextMenu(ContextMenu menu) {
// super.onCreateContextMenu(menu);
// }
@Override
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);
HitTestResult result = getHitTestResult();
switch (result.getType()) {
case HitTestResult.SRC_ANCHOR_TYPE:
case HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
String href = result.getExtra();
menu.setHeaderTitle(href);
menu.add("Copy URL").setOnMenuItemClickListener(menuItem -> {
ApiUtils.writeToClipboard(context, href);
return false;
});
menu.add("Open or Share Externally").setOnMenuItemClickListener(menuItem -> {
ApiUtils.openOrShareUrl(context, Uri.parse(href));
return false;
});
break;
}
}
private void applyConfig(Context context) {
WebSettings webSettings = this.getSettings();
@ -41,6 +60,12 @@ public class SpaccWebView extends WebView {
}
ApiUtils.apiRun(3, () -> webSettings.setAllowFileAccess(false));
webSettings.setStandardFontFamily(config.getStandardFontFamily());
ApiUtils.apiRun(3, () -> webSettings.setUserAgentString(config.getUserAgent()));
ApiUtils.apiRun(3, () -> webSettings.setBuiltInZoomControls(config.getAllowZoomControls()));
ApiUtils.apiRun(11, () -> webSettings.setDisplayZoomControls(config.getDisplayZoomControls()));
}
public void loadConfig(Context context, int configResource) {

View File

@ -4,16 +4,18 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
public class WebViewClient extends android.webkit.WebViewClient {
public class SpaccWebViewClient extends WebViewClient {
private final Context context;
public WebViewClient(Context context) {
public SpaccWebViewClient(Context context) {
super();
this.context = context;
}

View File

@ -1,32 +0,0 @@
package org.eu.spacc.spaccdotweb.android.webview;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.webkit.ValueCallback;
import android.webkit.WebView;
public class WebChromeClient extends android.webkit.WebChromeClient {
private static final int INPUT_FILE_REQUEST_CODE = 1;
private final Context context;
public WebChromeClient(Context context) {
super();
this.context = context;
}
// TODO: This only opens a file selector but then doesn't do anything
// TODO: Android < 5 support
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
contentSelectionIntent.setType("*/*"); // TODO: Read type from HTML input
Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
((Activity)context).startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
return true;
}
}

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<config>
<boolean name="allow_javascript">true</boolean>
<boolean name="allow_storage">true</boolean>
<AppIndex name="app_index">local</AppIndex> <!-- local or remote -->
<string name="local_index">index.html</string>
<string name="remote_index">https://example.com</string>
<!-- <string name="standard_font_family"></string> -->
<!-- <string name="user_agent"></string> -->
</config>

View File

@ -1,4 +1,4 @@
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
}

View File

@ -5,6 +5,10 @@ pluginManagement {
mavenCentral()
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {