diff --git a/SpaccDotWeb.Android/.gitignore b/SpaccDotWeb.Android/.gitignore
index 878c2cd..5d172f4 100644
--- a/SpaccDotWeb.Android/.gitignore
+++ b/SpaccDotWeb.Android/.gitignore
@@ -1,6 +1,7 @@
*.iml
.gradle
/local.properties
+/keystore.properties
.idea
.DS_Store
/build
@@ -8,3 +9,4 @@
.externalNativeBuild
.cxx
local.properties
+keystore.properties
diff --git a/SpaccDotWeb.Android/app/build.gradle b/SpaccDotWeb.Android/app/build.gradle
index ef53b7f..217bba2 100644
--- a/SpaccDotWeb.Android/app/build.gradle
+++ b/SpaccDotWeb.Android/app/build.gradle
@@ -3,29 +3,45 @@ plugins {
}
android {
- // Adjust the very following based on the application to ship
+ // Adjust namespace and defaultConfig very following based on the application to ship
+
namespace 'com.example.spaccwebviewapplication'
+
defaultConfig {
applicationId 'com.example.spaccwebviewapplication'
versionCode 1
versionName '1.0'
-
minSdk 1
+ //noinspection OldTargetApi
targetSdk 34
+ compileSdk 34
+ }
+
+ signingConfigs {
+ release {
+ def Properties localProps = new Properties()
+ def Properties keyProps = new Properties()
+ localProps.load(new FileInputStream(file('../local.properties')))
+ keyProps.load(new FileInputStream(file('../keystore.properties')))
+ storeFile file(localProps['storeFile'])
+ storePassword keyProps['storePassword']
+ keyPassword keyProps['keyPassword']
+ keyAlias keyProps['keyAlias']
+ }
}
buildTypes {
debug {
applicationIdSuffix '.dbg'
+ versionNameSuffix '.debug'
+ signingConfig signingConfigs.debug
}
release {
minifyEnabled true
shrinkResources true
- signingConfig signingConfigs.debug
+ signingConfig signingConfigs.release
}
}
-
- compileSdk 34
}
java {
diff --git a/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml b/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml
index 64fd597..57efdbd 100644
--- a/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml
+++ b/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml
@@ -6,44 +6,56 @@
tools:ignore="UnusedAttribute">
-
+
-
-
+
+
-
+
+
+
+
+
+
+
+
+
-
+
diff --git a/SpaccDotWeb.Android/app/src/main/assets/index.html b/SpaccDotWeb.Android/app/src/main/assets/index.html
index 8e30cce..38fb0a0 100644
--- a/SpaccDotWeb.Android/app/src/main/assets/index.html
+++ b/SpaccDotWeb.Android/app/src/main/assets/index.html
@@ -4,6 +4,7 @@
SpaccWebView Example Android Application
Repository: https://gitlab.com/SpaccInc/SpaccDotWeb.
Tests
+
JavaScript
diff --git a/SpaccDotWeb.Android/app/src/main/java/com/example/spaccwebviewapplication/MainActivity.java b/SpaccDotWeb.Android/app/src/main/java/com/example/spaccwebviewapplication/MainActivity.java
index 9949ef4..502e43f 100644
--- a/SpaccDotWeb.Android/app/src/main/java/com/example/spaccwebviewapplication/MainActivity.java
+++ b/SpaccDotWeb.Android/app/src/main/java/com/example/spaccwebviewapplication/MainActivity.java
@@ -1,20 +1,145 @@
package com.example.spaccwebviewapplication;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.webkit.WebView;
+import android.widget.EditText;
+
+import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
import org.eu.spacc.spaccdotweb.android.helpers.DataMoveHelper;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
+import org.eu.spacc.spaccdotweb.android.webview.SpaccWebViewClient;
public class MainActivity extends SpaccWebViewActivity {
+ private ActionBar actionBar = null;
+ private Menu menu = null;
+ private long pageStartTime = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+ ApiUtils.apiRun(11, () -> this.actionBar = getActionBar());
+
DataMoveHelper.run(this, R.string.exit, R.string.move_app_data, R.string.move_app_data_info);
this.webView = findViewById(R.id.webview);
+ this.webView.setStrings(R.string.open_menu, R.string.open_externally_menu, R.string.copy_url_menu);
+ this.webView.setWebViewClient(new SpaccWebViewClient(this) {
+ @SuppressLint("UseRequiresApi")
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ pageStartTime = System.currentTimeMillis();
+ if (menu != null) {
+ menu.findItem(R.id.stop).setVisible(true);
+ menu.findItem(R.id.reload).setVisible(false);
+ menu.findItem(R.id.open_externally).setVisible(!ApiUtils.isInternalUrl(Uri.parse(url)));
+ menu.findItem(R.id.about_app).setVisible(webView.getConfig().getAboutPage() != null);
+ menu.findItem(R.id.backwards).setEnabled(webView.canGoBack());
+ menu.findItem(R.id.forward).setEnabled(webView.canGoForward());
+ }
+ if (actionBar != null) {
+ actionBar.setSubtitle(R.string.loading);
+ }
+// if (justStarted) {
+// new Handler().postDelayed(() -> Objects.requireNonNull(getActionBar()).hide(), 3000);
+// justStarted = false;
+// }
+ }
+
+ @SuppressLint("UseRequiresApi")
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ if (menu != null) {
+ menu.findItem(R.id.stop).setVisible(false);
+ menu.findItem(R.id.reload).setVisible(true);
+ }
+ if (actionBar != null && pageStartTime != 0) {
+ actionBar.setSubtitle("~" + (System.currentTimeMillis() - pageStartTime) + "ms");
+ }
+ }
+ });
this.webView.loadConfig(this, R.xml.app_config);
this.webView.loadAppIndex();
}
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main, menu);
+ if (actionBar == null) {
+ menu.findItem(R.id.hide).setVisible(false);
+ }
+ return super.onCreateOptionsMenu(this.menu = menu);
+ }
+
+ @SuppressLint({"NonConstantResourceId", "UseRequiresApi"})
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.stop:
+ this.webView.stopLoading();
+ break;
+ case R.id.reload:
+ this.webView.reload();
+ break;
+ case R.id.backwards:
+ this.webView.goBack();
+ break;
+ case R.id.forward:
+ this.webView.goForward();
+ break;
+ case R.id.copy_url:
+ ApiUtils.writeToClipboard(this, webView.getUrl());
+ break;
+ case R.id.open_externally:
+ ApiUtils.openOrShareUrl(this, Uri.parse(webView.getUrl()));
+ break;
+ case R.id.exec_script:
+ EditText scriptText = new EditText(this);
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.execute_javascript)
+ .setView(scriptText)
+ .setPositiveButton("OK", (dialogInterface, i) -> webView.injectScript(scriptText.getText().toString()))
+ .setNeutralButton(R.string.cancel, null)
+ .show();
+ break;
+ case R.id.hide:
+ actionBar.hide();
+ break;
+ case R.id.exit:
+ finish();
+ break;
+ case R.id.about_app:
+ ApiUtils.openOrShareUrl(this, Uri.parse(webView.getConfig().getAboutPage()));
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @SuppressLint("UseRequiresApi")
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public void onBackPressed() {
+ if (actionBar == null) {
+ super.onBackPressed();
+ } else if (actionBar.isShowing()) {
+ actionBar.hide();
+ } else {
+ actionBar.show();
+ }
+ }
}
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Config.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Config.java
index e150f73..36e13ed 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Config.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Config.java
@@ -1,7 +1,5 @@
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;
@@ -14,6 +12,10 @@ public class Config extends Defaults {
this.configReader = configReader;
}
+ public String getAboutPage() {
+ return getString("about_page");
+ }
+
public Boolean getAllowJavascript() {
Boolean value = getBoolean("allow_javascript");
return (value != null ? value : Defaults.ALLOW_JAVASCRIPT);
@@ -24,6 +26,26 @@ public class Config extends Defaults {
return (value != null ? value : Defaults.ALLOW_STORAGE);
}
+ public Boolean getAllowAutoplay() {
+ Boolean value = getBoolean("allow_autoplay");
+ return (value != null ? value : Defaults.ALLOW_AUTOPLAY);
+ }
+
+ public Boolean getAllowDrmMedia() {
+ Boolean value = getBoolean("allow_drm_media");
+ return (value != null ? value : Defaults.ALLOW_DRM_MEDIA);
+ }
+
+ public Boolean getAllowAudioCapture() {
+ Boolean value = getBoolean("allow_audio_capture");
+ return (value != null ? value : Defaults.ALLOW_AUDIO_CAPTURE);
+ }
+
+ public Boolean getAllowVideoCapture() {
+ Boolean value = getBoolean("allow_video_capture");
+ return (value != null ? value : Defaults.ALLOW_VIDEO_CAPTURE);
+ }
+
public Boolean getAllowZoomControls() {
Boolean value = getBoolean("allow_zoom_controls");
return (value != null ? value : Defaults.ALLOW_ZOOM_CONTROLS);
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Defaults.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Defaults.java
index b3be7cf..6f1ec75 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Defaults.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Defaults.java
@@ -5,6 +5,10 @@ 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_AUTOPLAY = true;
+ public static final Boolean ALLOW_DRM_MEDIA = true;
+ public static final Boolean ALLOW_AUDIO_CAPTURE = false;
+ public static final Boolean ALLOW_VIDEO_CAPTURE = false;
public static final Boolean ALLOW_ZOOM_CONTROLS = false;
public static final Boolean DISPLAY_ZOOM_CONTROLS = false;
public static final AppIndex APP_INDEX = AppIndex.LOCAL;
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewActivity.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewActivity.java
index 0d3a056..dbe7caf 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewActivity.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewActivity.java
@@ -7,9 +7,10 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.webkit.ValueCallback;
+import java.io.File;
+
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;
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/ConfigReader.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/ConfigReader.java
index adfdd88..af8b86a 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/ConfigReader.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/ConfigReader.java
@@ -5,9 +5,10 @@ import android.content.res.XmlResourceParser;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-import org.eu.spacc.spaccdotweb.android.Constants.*;
import org.xmlpull.v1.XmlPullParserException;
+import org.eu.spacc.spaccdotweb.android.Constants.*;
+
public class ConfigReader {
private final Map configData = new HashMap<>();
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/DataMoveHelper.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/DataMoveHelper.java
index 2780f55..0a56566 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/DataMoveHelper.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/DataMoveHelper.java
@@ -3,18 +3,17 @@ package org.eu.spacc.spaccdotweb.android.helpers;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.os.Build;
+import java.io.IOException;
+
import org.eu.spacc.spaccdotweb.android.Constants;
import org.eu.spacc.spaccdotweb.android.utils.StorageUtils;
import org.eu.spacc.spaccdotweb.android.utils.FileUtils;
-import java.io.IOException;
public class DataMoveHelper {
public static void run(Context context, int labelExit, int dialogTitle, int dialogMessage) {
- Activity activity = (Activity)context;
SharedPrefHelper sharedPrefHelper = new SharedPrefHelper(context);
Constants.DataLocation dataLocationReal = (StorageUtils.isInstalledOnExternalStorage(context) ? Constants.DataLocation.EXTERNAL : Constants.DataLocation.INTERNAL);
Integer dataLocationSaved = sharedPrefHelper.getInt("data_location");
@@ -22,29 +21,21 @@ public class DataMoveHelper {
sharedPrefHelper.setInt("data_location", dataLocationReal.ordinal());
} else if (!dataLocationSaved.equals(dataLocationReal.ordinal())) {
new AlertDialog.Builder(context)
- .setTitle(dialogTitle)
- .setMessage(dialogMessage)
- .setCancelable(false)
- .setNegativeButton(labelExit, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- ((Activity)context).finish();
- }
- })
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int i) {
- // TODO: Check that the storage locations are all present to copy, and implement an error dialog
- try {
- FileUtils.moveDirectory(StorageUtils.dataDirFromEnum(context, Constants.DataLocation.values()[dataLocationSaved]), StorageUtils.dataDirFromEnum(context, dataLocationReal), false);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- sharedPrefHelper.setInt("data_location", dataLocationReal.ordinal());
- restartActivity(context);
- }
- })
- .show();
+ .setTitle(dialogTitle)
+ .setMessage(dialogMessage)
+ .setCancelable(false)
+ .setNegativeButton(labelExit, (dialogInterface, i) -> ((Activity)context).finish())
+ .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
+ // TODO: Check that the storage locations are all present to copy, and implement an error dialog
+ try {
+ FileUtils.moveDirectory(StorageUtils.dataDirFromEnum(context, Constants.DataLocation.values()[dataLocationSaved]), StorageUtils.dataDirFromEnum(context, dataLocationReal), false);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ sharedPrefHelper.setInt("data_location", dataLocationReal.ordinal());
+ restartActivity(context);
+ })
+ .show();
}
}
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/SharedPrefHelper.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/SharedPrefHelper.java
index 658ac43..7959d42 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/SharedPrefHelper.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/SharedPrefHelper.java
@@ -3,6 +3,9 @@ package org.eu.spacc.spaccdotweb.android.helpers;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
+import org.json.JSONArray;
+import org.json.JSONException;
+import java.util.ArrayList;
public class SharedPrefHelper {
private final SharedPreferences sharedPref;
@@ -11,8 +14,16 @@ public class SharedPrefHelper {
this.sharedPref = context.getSharedPreferences("SpaccWebView", Context.MODE_PRIVATE);
}
+ public SharedPrefHelper(Context context, String name) {
+ this.sharedPref = context.getSharedPreferences(name, Context.MODE_PRIVATE);
+ }
+
public Integer getInt(String name) {
- Integer value = (Integer)sharedPref.getInt(name, -1);
+ return getInt(name, -1);
+ }
+
+ public Integer getInt(String name, int fallback) {
+ Integer value = (Integer)sharedPref.getInt(name, fallback);
return (value != -1 ? value : null);
}
@@ -24,4 +35,30 @@ public class SharedPrefHelper {
editor.commit();
}
}
+
+ public ArrayList getStringList(String name) {
+ try {
+ String json = sharedPref.getString(name, null);
+ if (json != null) {
+ JSONArray parsed = new JSONArray(json);
+ ArrayList restored = new ArrayList<>(parsed.length());
+ for (int i = 0; i < parsed.length(); i++) {
+ restored.add(parsed.getString(i));
+ }
+ return restored;
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+ public void setStringList(String name, ArrayList list) {
+ SharedPreferences.Editor editor = sharedPref.edit().putString(name, new JSONArray(list).toString());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ editor.apply();
+ } else {
+ editor.commit();
+ }
+ }
}
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/ApiUtils.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/ApiUtils.java
index 8f6a7b5..0b45101 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/ApiUtils.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/ApiUtils.java
@@ -14,14 +14,19 @@ public class ApiUtils {
}
}
+ public static Boolean isInternalUrl(Uri url) {
+ return url.toString().startsWith("file:///android_asset/");
+ }
+
public static void openOrShareUrl(Context context, Uri url) {
- try {
- // Open the URL externally
- context.startActivity(new Intent(Intent.ACTION_VIEW, url));
- } catch (ActivityNotFoundException ignored) {
- // No app can handle it, so share it instead
- context.startActivity(new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, url.toString()));
+ if (!isInternalUrl(url)) {
+ try { // Open the URL externally
+ context.startActivity(new Intent(Intent.ACTION_VIEW, url));
+ return;
+ } catch (ActivityNotFoundException ignored) {}
}
+ // No app can handle it, so share it instead
+ context.startActivity(new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, url.toString()));
}
public static void writeToClipboard(Context context, String text) {
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/FileUtils.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/FileUtils.java
index e89508e..33e1da7 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/FileUtils.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/FileUtils.java
@@ -6,6 +6,7 @@ import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.webkit.CookieManager;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -67,6 +68,24 @@ public class FileUtils {
}
}
+ /* https://gist.github.com/defHLT/3ac50c765f3cf289da03 */
+ public static String inputStreamToString(InputStream inputStream) {
+ String res = null;
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ byte[] b = new byte[1];
+ try {
+ while (inputStream.read(b) != -1) {
+ outputStream.write(b);
+ }
+ res = outputStream.toString();
+ inputStream.close();
+ outputStream.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return res;
+ }
+
// TODO: Handle downloads internally on old Android versions
public static void startFileDownload(Context context, Uri downloadUrl, String userAgent, String contentDisposition, String mimeType) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD && !downloadUrl.toString().toLowerCase().startsWith("data:")) {
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/StorageUtils.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/StorageUtils.java
index da45209..0363b30 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/StorageUtils.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/StorageUtils.java
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import java.io.File;
+
import org.eu.spacc.spaccdotweb.android.Constants.*;
public class StorageUtils {
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadListener.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadListener.java
index 56d3dde..4d85aaa 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadListener.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadListener.java
@@ -2,6 +2,7 @@ package org.eu.spacc.spaccdotweb.android.webview;
import android.content.Context;
import android.net.Uri;
+
import org.eu.spacc.spaccdotweb.android.utils.FileUtils;
public class DownloadListener implements android.webkit.DownloadListener {
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebChromeClient.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebChromeClient.java
index 5bdd846..072d7a4 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebChromeClient.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebChromeClient.java
@@ -1,24 +1,37 @@
package org.eu.spacc.spaccdotweb.android.webview;
import android.annotation.TargetApi;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
+import android.webkit.PermissionRequest;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eu.spacc.spaccdotweb.android.Config;
import org.eu.spacc.spaccdotweb.android.Constants;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
+import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
public class SpaccWebChromeClient extends WebChromeClient {
private final SpaccWebViewActivity activity;
+ private Config config;
public SpaccWebChromeClient(SpaccWebViewActivity activity) {
super();
this.activity = activity;
}
+ public void applyConfig(Config config) {
+ this.config = config;
+ }
+
// TODO: Android < 4 support
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -37,4 +50,27 @@ public class SpaccWebChromeClient extends WebChromeClient {
activity.fileUploadCallback = valueCallback;
activity.startActivityForResult(Intent.createChooser(intent, null), Constants.ActivityCodes.UPLOAD_FILE.ordinal());
}
+
+ @Override
+ public void onPermissionRequest(PermissionRequest request) {
+ AtomicBoolean handled = new AtomicBoolean(false);
+ ApiUtils.apiRun(21, () -> {
+ ArrayList granted = new ArrayList<>();
+ for (String resource: request.getResources()) {
+ if ((resource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID) && config.getAllowDrmMedia()) ||
+ (resource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE) && config.getAllowAudioCapture()) ||
+ (resource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE) && config.getAllowVideoCapture())
+ ) {
+ granted.add(resource);
+ }
+ }
+ if (!granted.isEmpty()) {
+ request.grant(granted.toArray(new String[0]));
+ handled.set(true);
+ }
+ });
+ if (!handled.get()) {
+ super.onPermissionRequest(request);
+ }
+ }
}
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebView.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebView.java
index fb00899..33c0bbc 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebView.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebView.java
@@ -2,10 +2,15 @@ package org.eu.spacc.spaccdotweb.android.webview;
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
import android.util.AttributeSet;
import android.view.ContextMenu;
+import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import java.util.ArrayList;
+
import org.eu.spacc.spaccdotweb.android.Config;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
import org.eu.spacc.spaccdotweb.android.helpers.ConfigReader;
@@ -14,36 +19,111 @@ import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
public class SpaccWebView extends WebView {
private Config config;
private Context context;
+ private SpaccWebViewClient webViewClient;
+ private SpaccWebChromeClient webChromeClient;
+
+ private int openString;
+ private int openExternallyString;
+ private int copyUrlString;
+
+ private Boolean isLoaded = false;
+ protected ArrayList scriptQueue = new ArrayList<>();
+
+ public SpaccWebView(Context context) {
+ super(context);
+ setup(context);
+ }
public SpaccWebView(Context context, AttributeSet attrs) {
super(context, attrs);
+ setup(context);
+ }
+
+ private void setup(Context context) {
this.context = context;
- this.setWebViewClient(new SpaccWebViewClient(context));
- this.setWebChromeClient(new SpaccWebChromeClient((SpaccWebViewActivity)context));
+ this.setWebViewClient(this.webViewClient = new SpaccWebViewClient(context));
+ this.setWebChromeClient(this.webChromeClient = new SpaccWebChromeClient((SpaccWebViewActivity)context));
this.setDownloadListener(new DownloadListener(context));
this.config = new Config();
this.applyConfig(context);
}
+ public void setStrings(int open, int openExternally, int copyUrl) {
+ openString = open;
+ openExternallyString = openExternally;
+ copyUrlString = copyUrl;
+ }
+
+ @Override
+ public void setWebViewClient(WebViewClient client) {
+ super.setWebViewClient(client);
+ webViewClient = (SpaccWebViewClient)client;
+ webViewClient.applyConfig(config);
+ }
+
+ @Override
+ public void setWebChromeClient(WebChromeClient client) {
+ super.setWebChromeClient(client);
+ webChromeClient = (SpaccWebChromeClient)client;
+ webChromeClient.applyConfig(config);
+ }
+
// TODO: Implement context menu (long-press on links, images, etc...)
@Override
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);
HitTestResult result = getHitTestResult();
- switch (result.getType()) {
+ /*switch (result.getType()) {
+ case HitTestResult.UNKNOWN_TYPE:
+ case HitTestResult.IMAGE_TYPE:
case HitTestResult.SRC_ANCHOR_TYPE:
- case HitTestResult.SRC_IMAGE_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;
+ if (href != null) {
+ menu.setHeaderTitle(href);
+ menu.add(openString).setOnMenuItemClickListener(menuItem -> {
+ if (!webViewClient.shouldOverrideUrlLoading(this, href)) {
+ this.loadUrl(href);
+ }
+ return false;
+ });
+ if (!ApiUtils.isInternalUrl(Uri.parse(href))) {
+ menu.add(openExternallyString).setOnMenuItemClickListener(menuItem -> {
+ ApiUtils.openOrShareUrl(context, Uri.parse(href));
+ return false;
+ });
+ }
+ menu.add(copyUrlString).setOnMenuItemClickListener(menuItem -> {
+ ApiUtils.writeToClipboard(context, href);
+ return false;
+ });
+ }
+ /*break;
+ }*/
+ }
+
+ public void injectScript(String script) {
+ if (isLoaded) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ this.evaluateJavascript(script, null);
+ } else {
+ this.loadUrl("javascript:(function(){" + script + "})();");
+ }
+ } else {
+ scriptQueue.add(script);
+ }
+ }
+
+ public void injectStyle(String style) {
+ injectScript("document.head.appendChild(Object.assign(document.createElement('style'),{innerHTML:\"" + style + "\"}))");
+ }
+
+ protected void setLoaded(Boolean loaded) {
+ if (isLoaded = loaded) {
+ for (String script : scriptQueue) {
+ injectScript(script);
+ }
+ scriptQueue.clear();
}
}
@@ -61,11 +141,16 @@ public class SpaccWebView extends WebView {
ApiUtils.apiRun(3, () -> webSettings.setAllowFileAccess(false));
+ ApiUtils.apiRun(17, () -> webSettings.setMediaPlaybackRequiresUserGesture(!config.getAllowAutoplay()));
+
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()));
+
+ webViewClient.applyConfig(config);
+ webChromeClient.applyConfig(config);
}
public void loadConfig(Context context, int configResource) {
@@ -73,6 +158,10 @@ public class SpaccWebView extends WebView {
this.applyConfig(context);
}
+ public Config getConfig() {
+ return config;
+ }
+
public void loadAppIndex() {
String url = null;
switch (config.getAppIndex()) {
diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebViewClient.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebViewClient.java
index d6349dd..6d8ae5c 100644
--- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebViewClient.java
+++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebViewClient.java
@@ -2,31 +2,49 @@ package org.eu.spacc.spaccdotweb.android.webview;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Bitmap;
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;
+
+import org.eu.spacc.spaccdotweb.android.Config;
+import org.eu.spacc.spaccdotweb.android.Constants;
+import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
public class SpaccWebViewClient extends WebViewClient {
private final Context context;
+ private Config config;
public SpaccWebViewClient(Context context) {
super();
this.context = context;
}
+ public void applyConfig(Config config) {
+ this.config = config;
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ ((SpaccWebView)view).setLoaded(false);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ ((SpaccWebView)view).setLoaded(true);
+ }
+
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// TODO: This should not override all HTTP links if the app loads from remote (which will allow proper internal navigation and file downloads)
// NOTE: It seems like the WebView overrides loading of data: URIs before we can get it here...
- List externalProtocols = Arrays.asList("data", "http", "https", "mailto", "ftp");
+ // List externalProtocols = Arrays.asList("data", "http", "https", "mailto", "ftp");
String protocol = url.toLowerCase().split(":")[0];
- if (protocol.equals("file")) {
+ if (protocol.equals("file") || (config.getAppIndex() == Constants.AppIndex.REMOTE && Arrays.asList("http", "https").contains(protocol))) {
return super.shouldOverrideUrlLoading(view, url);
} else if (protocol.equals("intent")) {
ApiUtils.apiRun(4, () -> {
@@ -42,6 +60,5 @@ public class SpaccWebViewClient extends WebViewClient {
ApiUtils.openOrShareUrl(context, Uri.parse(url));
return true;
}
-
}
}
diff --git a/SpaccDotWeb.Android/app/src/main/res/layout/activity_main.xml b/SpaccDotWeb.Android/app/src/main/res/layout/activity_main.xml
index dd37b98..89a83e7 100644
--- a/SpaccDotWeb.Android/app/src/main/res/layout/activity_main.xml
+++ b/SpaccDotWeb.Android/app/src/main/res/layout/activity_main.xml
@@ -2,6 +2,7 @@
diff --git a/SpaccDotWeb.Android/app/src/main/res/menu/main.xml b/SpaccDotWeb.Android/app/src/main/res/menu/main.xml
new file mode 100644
index 0000000..3c2567b
--- /dev/null
+++ b/SpaccDotWeb.Android/app/src/main/res/menu/main.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/SpaccDotWeb.Android/app/src/main/res/values-it/strings.xml b/SpaccDotWeb.Android/app/src/main/res/values-it/strings.xml
index 1602d42..3cdec3b 100644
--- a/SpaccDotWeb.Android/app/src/main/res/values-it/strings.xml
+++ b/SpaccDotWeb.Android/app/src/main/res/values-it/strings.xml
@@ -1,6 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+]>
- Esci
+ &about_app;
+ โน๏ธ &about_app;
+ Yes
+ No
+ Cancel
+ &exit;
+ ๐ช &exit;
+ &hide;
+ ๐ณ๏ธ &hide;
Sposta Dati App
La app รจ stata trasferita su una diversa locazione di archiviazione. I dati saranno spostati ora.
+ Caricamento
+ &reload;
+ ๐ &reload;
+ &stop_loading;
+ ๐ &stop_loading;
+ &backwards;
+ โฎ๏ธ &backwards;
+ &forward;
+ โญ๏ธ &forward;
+ &open;
+ ๐ &open;
+ &open_externally;
+ โ๏ธ &open_externally;
+ ©_url;
+ ๐ ©_url;
+ &execute_javascript;
+ ๐ฃ &execute_javascript;
\ No newline at end of file
diff --git a/SpaccDotWeb.Android/app/src/main/res/values/strings.xml b/SpaccDotWeb.Android/app/src/main/res/values/strings.xml
index e02a8f5..2667c29 100644
--- a/SpaccDotWeb.Android/app/src/main/res/values/strings.xml
+++ b/SpaccDotWeb.Android/app/src/main/res/values/strings.xml
@@ -1,7 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+]>
- SpaccWebView Application
- Exit
+ &app_name;
+ &about_app;
+ โน๏ธ &about_app;
+ Yes
+ No
+ Cancel
+ &exit;
+ ๐ช &exit;
+ &hide;
+ ๐ณ๏ธ &hide;
Move App Data
The app has been transferred to a different storage location. The data will be moved now.
+ Loading
+ &reload;
+ ๐ &reload;
+ &stop_loading;
+ ๐ &stop_loading;
+ &backwards;
+ โฎ๏ธ &backwards;
+ &forward;
+ โญ๏ธ &forward;
+ &open;
+ ๐ &open;
+ &open_externally;
+ โ๏ธ &open_externally;
+ ©_url;
+ ๐ ©_url;
+ &execute_javascript;
+ ๐ฃ &execute_javascript;
\ No newline at end of file
diff --git a/SpaccDotWeb.Android/app/src/main/res/xml/app_config.xml b/SpaccDotWeb.Android/app/src/main/res/xml/app_config.xml
index 036e435..235b50a 100644
--- a/SpaccDotWeb.Android/app/src/main/res/xml/app_config.xml
+++ b/SpaccDotWeb.Android/app/src/main/res/xml/app_config.xml
@@ -1,8 +1,21 @@
+
+
local
index.html
- https://example.com
+ https://example.com/
+
+ true
+ true
+ true
+ true
+ false
+ false
+
+ false
+ false
+
\ No newline at end of file
diff --git a/SpaccDotWeb.Android/settings.gradle b/SpaccDotWeb.Android/settings.gradle
index cc1c385..07c9759 100644
--- a/SpaccDotWeb.Android/settings.gradle
+++ b/SpaccDotWeb.Android/settings.gradle
@@ -9,6 +9,7 @@ pluginManagement {
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
+
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
@@ -16,5 +17,6 @@ dependencyResolutionManagement {
mavenCentral()
}
}
+
rootProject.name = 'SpaccWebView Application'
include ':app'