diff --git a/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml b/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml index b822565..966dd2b 100644 --- a/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml +++ b/SpaccDotWeb.Android/app/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ - + diff --git a/SpaccDotWeb.Android/app/src/main/assets/index.html b/SpaccDotWeb.Android/app/src/main/assets/index.html index 6b6cfa9..245daa3 100644 --- a/SpaccDotWeb.Android/app/src/main/assets/index.html +++ b/SpaccDotWeb.Android/app/src/main/assets/index.html @@ -21,10 +21,18 @@
  • http://
  • https://
  • ftp://
  • +
  • data:text/plain
  • +
  • data:text/html
  • mailto:
  • Files

    Upload:

    -

    Download: Download

    +

    Download:

    + \ No newline at end of file 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 a763913..311755c 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,7 +1,9 @@ package com.example.spaccwebviewapplication; import android.os.Bundle; -import org.eu.spacc.spaccdotweb.android.*; + +import org.eu.spacc.spaccdotweb.android.helpers.DataMoveHelper; +import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity; public class MainActivity extends SpaccWebViewActivity { diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/ApiUtils.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/ApiUtils.java deleted file mode 100644 index 2419569..0000000 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/ApiUtils.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.eu.spacc.spaccdotweb.android; - -import android.os.Build; - -public class ApiUtils { - - public static void apiRun(int apiLevel, Runnable action) { - if (Build.VERSION.SDK_INT >= apiLevel) { - action.run(); - } - } -} diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Constants.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Constants.java index f4336d4..ed963fc 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Constants.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/Constants.java @@ -1,6 +1,7 @@ package org.eu.spacc.spaccdotweb.android; public class Constants { + //public enum ActivityCodes { DOWNLOAD_FILE, UPLOAD_FILE } public enum AppIndex { LOCAL, REMOTE } public enum DataLocation { INTERNAL, EXTERNAL } } 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 6096540..2f866f9 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 @@ -2,11 +2,23 @@ package org.eu.spacc.spaccdotweb.android; import android.annotation.SuppressLint; import android.app.Activity; +import org.eu.spacc.spaccdotweb.android.webview.SpaccWebView; import java.io.File; public class SpaccWebViewActivity extends Activity { protected SpaccWebView webView; +// @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())); +// } +// } +// } + @Override public void onBackPressed() { if (this.webView.canGoBack()) { @@ -16,6 +28,16 @@ public class SpaccWebViewActivity extends Activity { } } +// // TODO: Find some way to download to any storage location with DownloadManager, since it doesn't take content:// URIs +// private void enqueueDownload(Uri fileUri) { +// DownloadDataHolder downloadDataHolder = DownloadDataHolder.getInstance(); +// FileUtils.startFileDownload(this, +// downloadDataHolder.getDownloadUrl(), +// downloadDataHolder.getContentDisposition(), +// downloadDataHolder.getUserAgent(), +// downloadDataHolder.getMimeType()); +// } + @SuppressLint("NewApi") // We have our custom implementation @Override public File getDataDir() { diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewApplication.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewApplication.java index eb2463f..52e453f 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewApplication.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewApplication.java @@ -1,6 +1,7 @@ package org.eu.spacc.spaccdotweb.android; import android.app.Application; +import org.eu.spacc.spaccdotweb.android.utils.StorageUtils; import java.io.File; public class SpaccWebViewApplication extends Application { diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewClient.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewClient.java deleted file mode 100644 index a962a18..0000000 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebViewClient.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.eu.spacc.spaccdotweb.android; - -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import java.util.Arrays; -import java.util.List; - -public class SpaccWebViewClient extends WebViewClient { - private final Context context; - - public SpaccWebViewClient(Context context) { - super(); - this.context = context; - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - // TODO: This should not override all HTTP links if the app loads from remote - List protocols = Arrays.asList("http", "https", "mailto", "ftp"); - if (protocols.contains(url.toLowerCase().split(":")[0])) { - try { - // Open the link externally - context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(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)); - } - return true; - } else { - return super.shouldOverrideUrlLoading(view, url); - } - } -} diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/DataMoveHelper.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/DataMoveHelper.java similarity index 92% rename from SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/DataMoveHelper.java rename to SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/DataMoveHelper.java index c565bc1..f3a1a48 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/DataMoveHelper.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/DataMoveHelper.java @@ -1,4 +1,4 @@ -package org.eu.spacc.spaccdotweb.android; +package org.eu.spacc.spaccdotweb.android.helpers; import android.app.Activity; import android.app.AlertDialog; @@ -6,6 +6,11 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Build; + +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 { diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SharedPrefHelper.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/SharedPrefHelper.java similarity index 93% rename from SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SharedPrefHelper.java rename to SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/SharedPrefHelper.java index 0f912fa..658ac43 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SharedPrefHelper.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/helpers/SharedPrefHelper.java @@ -1,4 +1,4 @@ -package org.eu.spacc.spaccdotweb.android; +package org.eu.spacc.spaccdotweb.android.helpers; import android.content.Context; import android.content.SharedPreferences; 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 new file mode 100644 index 0000000..f40beca --- /dev/null +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/ApiUtils.java @@ -0,0 +1,26 @@ +package org.eu.spacc.spaccdotweb.android.utils; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; + +public class ApiUtils { + + public static void apiRun(int apiLevel, Runnable action) { + if (Build.VERSION.SDK_INT >= apiLevel) { + action.run(); + } + } + + 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())); + } + } +} diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/FileUtils.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/FileUtils.java similarity index 58% rename from SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/FileUtils.java rename to SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/FileUtils.java index f90b4c6..3b8c7fe 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/FileUtils.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/FileUtils.java @@ -1,5 +1,12 @@ -package org.eu.spacc.spaccdotweb.android; +package org.eu.spacc.spaccdotweb.android.utils; +import static android.content.Context.DOWNLOAD_SERVICE; +import android.app.DownloadManager; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.webkit.CookieManager; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -60,4 +67,21 @@ public class FileUtils { rootDir.delete(); } } + + // 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:")) { + // TODO: We should handle downloading data: URIs manually + DownloadManager.Request request = new DownloadManager.Request(downloadUrl) + //.setDestinationUri(fileUri) + .setMimeType(mimeType) + .addRequestHeader("User-Agent", userAgent) + .addRequestHeader("Content-Disposition", contentDisposition) + .addRequestHeader("Cookie", CookieManager.getInstance().getCookie(downloadUrl.toString())); + ApiUtils.apiRun(11, () -> request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)); + ((DownloadManager)context.getSystemService(DOWNLOAD_SERVICE)).enqueue(request); + } else { + ApiUtils.openOrShareUrl(context, downloadUrl); + } + } } diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/StorageUtils.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/StorageUtils.java similarity index 98% rename from SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/StorageUtils.java rename to SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/StorageUtils.java index 939cf34..da45209 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/StorageUtils.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/utils/StorageUtils.java @@ -1,4 +1,4 @@ -package org.eu.spacc.spaccdotweb.android; +package org.eu.spacc.spaccdotweb.android.utils; import android.content.Context; import android.content.pm.ApplicationInfo; diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadDataHolder.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadDataHolder.java new file mode 100644 index 0000000..68d3994 --- /dev/null +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadDataHolder.java @@ -0,0 +1,46 @@ +package org.eu.spacc.spaccdotweb.android.webview; + +import android.net.Uri; + +public class DownloadDataHolder { + private static DownloadDataHolder instance; + private Uri downloadUrl; + private String userAgent; + private String contentDisposition; + private String mimeType; + + public static synchronized DownloadDataHolder getInstance() { + if (instance == null) { + instance = new DownloadDataHolder(); + } + return instance; + } + + public void setData(Uri downloadUrl, String userAgent, String contentDisposition, String mimeType, long contentLength) { + this.downloadUrl = downloadUrl; + this.userAgent = userAgent; + this.contentDisposition = contentDisposition; + this.mimeType = mimeType; + } + + public void clearData() { + instance = new DownloadDataHolder(); + } + + public Uri getDownloadUrl() { + return downloadUrl; + } + + public String getUserAgent() { + return userAgent; + } + + public String getContentDisposition() { + return contentDisposition; + } + + public String getMimeType() { + return mimeType; + } +} + 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 new file mode 100644 index 0000000..56d3dde --- /dev/null +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/DownloadListener.java @@ -0,0 +1,27 @@ +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 { + private final Context context; + + public DownloadListener(Context context) { + this.context = context; + } + + // TODO: Read file name from download="..." HTML attribute when present + // TODO: Implement file destination path picking (requires Android < 5 with SAF Intent) + @Override + public void onDownloadStart(String downloadUrl, String userAgent, String contentDisposition, String mimeType, long contentLength) { +// String[] nameParts = downloadUrl.split("/"); +// Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) +// .addCategory(Intent.CATEGORY_OPENABLE) +// .setType(mimeType) +// .putExtra(Intent.EXTRA_TITLE, nameParts[nameParts.length - 1]); +// DownloadDataHolder.getInstance().setData(Uri.parse(downloadUrl), userAgent, contentDisposition, mimeType, contentLength); +// ((Activity)context).startActivityForResult(intent, Constants.CREATE_FILE_REQUEST_CODE); + FileUtils.startFileDownload(context, Uri.parse(downloadUrl), userAgent, contentDisposition, mimeType); + } +} diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebView.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebView.java similarity index 68% rename from SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebView.java rename to SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebView.java index e5c0eb2..c5cfa25 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebView.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/SpaccWebView.java @@ -1,18 +1,23 @@ -package org.eu.spacc.spaccdotweb.android; +package org.eu.spacc.spaccdotweb.android.webview; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; +import android.view.ContextMenu; import android.webkit.WebSettings; import android.webkit.WebView; +import org.eu.spacc.spaccdotweb.android.utils.ApiUtils; +import org.eu.spacc.spaccdotweb.android.Config; + public class SpaccWebView extends WebView { @SuppressLint("SetJavaScriptEnabled") public SpaccWebView(Context context, AttributeSet attrs) { super(context, attrs); - this.setWebViewClient(new SpaccWebViewClient(context)); - this.setWebChromeClient(new SpaccWebChromeClient(context)); + this.setWebViewClient(new WebViewClient(context)); + this.setWebChromeClient(new WebChromeClient(context)); + this.setDownloadListener(new DownloadListener(context)); WebSettings webSettings = this.getSettings(); @@ -27,6 +32,12 @@ public class SpaccWebView extends WebView { ApiUtils.apiRun(3, () -> webSettings.setAllowFileAccess(false)); } + // TODO: Implement context menu (long-press on links, images, etc...) +// @Override +// protected void onCreateContextMenu(ContextMenu menu) { +// super.onCreateContextMenu(menu); +// } + public void loadAppIndex() { String url = null; switch (Config.APP_INDEX) { diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebChromeClient.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/WebChromeClient.java similarity index 84% rename from SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebChromeClient.java rename to SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/WebChromeClient.java index 34a949b..3cf9f5c 100644 --- a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/SpaccWebChromeClient.java +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/WebChromeClient.java @@ -1,19 +1,18 @@ -package org.eu.spacc.spaccdotweb.android; +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.WebChromeClient; import android.webkit.WebView; -public class SpaccWebChromeClient extends WebChromeClient { +public class WebChromeClient extends android.webkit.WebChromeClient { private static final int INPUT_FILE_REQUEST_CODE = 1; private final Context context; - public SpaccWebChromeClient(Context context) { + public WebChromeClient(Context context) { super(); this.context = context; } diff --git a/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/WebViewClient.java b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/WebViewClient.java new file mode 100644 index 0000000..10cb877 --- /dev/null +++ b/SpaccDotWeb.Android/app/src/main/java/org/eu/spacc/spaccdotweb/android/webview/WebViewClient.java @@ -0,0 +1,34 @@ +package org.eu.spacc.spaccdotweb.android.webview; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.webkit.WebView; + +import org.eu.spacc.spaccdotweb.android.utils.ApiUtils; + +import java.util.Arrays; +import java.util.List; + +public class WebViewClient extends android.webkit.WebViewClient { + private final Context context; + + public WebViewClient(Context context) { + super(); + this.context = context; + } + + @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 protocols = Arrays.asList("data", "http", "https", "mailto", "ftp"); + if (protocols.contains(url.toLowerCase().split(":")[0])) { + ApiUtils.openOrShareUrl(context, Uri.parse(url)); + return true; + } else { + return super.shouldOverrideUrlLoading(view, url); + } + } +} 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 8cda6f7..dd37b98 100644 --- a/SpaccDotWeb.Android/app/src/main/res/layout/activity_main.xml +++ b/SpaccDotWeb.Android/app/src/main/res/layout/activity_main.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> -