[SpaccWebView Android] Initial external storage support, JS alerts working, other stuff
This commit is contained in:
parent
fd0151b999
commit
b582399d06
|
@ -20,6 +20,7 @@ android {
|
||||||
}
|
}
|
||||||
release {
|
release {
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,23 @@
|
||||||
<!-- Lets the app access the Internet — not needed for fully offline apps -->
|
<!-- Lets the app access the Internet — not needed for fully offline apps -->
|
||||||
<!-- <uses-permission android:name="android.permission.INTERNET" /> -->
|
<!-- <uses-permission android:name="android.permission.INTERNET" /> -->
|
||||||
|
|
||||||
<!-- Lets the webview load and display files from the device's shared storage -->
|
<!-- Needed from Android ??? to 4.4 KitKat (API ???-19) to keep app data on external storage -->
|
||||||
<!-- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> -->
|
<!-- Removing these will not break the app, but it will write only on internal storage -->
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<!-- Additional suggested attributes:
|
<!-- Additional suggested attributes:
|
||||||
android:appCategory=["accessibility" | "audio" | "game" | "image" | "maps" | "news" | "productivity" | "social" | "video"]
|
android:appCategory=["accessibility" | "audio" | "game" | "image" | "maps" | "news" | "productivity" | "social" | "video"]
|
||||||
android:isGame=["true" | "false"]
|
android:isGame=["true" | "false"]
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:usesCleartextTraffic=["true" | "false"]
|
||||||
-->
|
-->
|
||||||
<application
|
<application
|
||||||
|
android:name=".MainApplication"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
|
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
|
||||||
|
android:resizeableActivity="true"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|
|
@ -1,3 +1,30 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<h1>SpaccWebView Application</h1>
|
<html lang="en">
|
||||||
<p><a href="https://gitlab.com/SpaccInc/SpaccDotWeb">https://gitlab.com/SpaccInc/SpaccDotWeb</a></p>
|
<body>
|
||||||
|
<h1>SpaccWebView Example Android Application</h1>
|
||||||
|
<p>Repository: <a href="https://gitlab.com/SpaccInc/SpaccDotWeb">https://gitlab.com/SpaccInc/SpaccDotWeb</a>.</p>
|
||||||
|
<h2>Tests</h2>
|
||||||
|
<h3>JavaScript</h3>
|
||||||
|
<p>
|
||||||
|
<label><input type="text" autocomplete="off" onkeypress="(function(event){if(event.keyCode===13)eval(event.target.value);})(event);"/></label>
|
||||||
|
<button onclick="eval(this.parentElement.querySelector('input').value);">Run</button>
|
||||||
|
</p>
|
||||||
|
<h3>Popups</h3>
|
||||||
|
<p>
|
||||||
|
<button onclick="alert(1);">alert()</button>
|
||||||
|
<button onclick="confirm(1);">confirm()</button>
|
||||||
|
<button onclick="prompt(1);">prompt()</button>
|
||||||
|
</p>
|
||||||
|
<h3>Links</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="file:///">file://</a></li>
|
||||||
|
<li><a href="http://example.com">http://</a></li>
|
||||||
|
<li><a href="https://example.com">https://</a></li>
|
||||||
|
<li><a href="ftp://example.com">ftp://</a></li>
|
||||||
|
<li><a href="mailto:example@example.com">mailto:</a></li>
|
||||||
|
</ul>
|
||||||
|
<h3>Files</h3>
|
||||||
|
<p>Upload: <label><input type="file"/></label></p>
|
||||||
|
<p>Download: <a download="test.bin" href="data:text/plain;utf8,Test">Download</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,28 +1,18 @@
|
||||||
package com.example.spaccwebviewapplication;
|
package com.example.spaccwebviewapplication;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import org.eu.spacc.spaccdotweb.android.*;
|
import org.eu.spacc.spaccdotweb.android.*;
|
||||||
|
|
||||||
public class MainActivity extends Activity {
|
public class MainActivity extends SpaccWebViewActivity {
|
||||||
private SpaccWebView webView;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
webView = findViewById(R.id.webview);
|
|
||||||
|
|
||||||
webView.loadAppIndex();
|
DataMoveHelper.run(this, R.string.exit, R.string.move_app_data, R.string.move_app_data_info);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
this.webView = findViewById(R.id.webview);
|
||||||
public void onBackPressed() {
|
this.webView.loadAppIndex();
|
||||||
if (webView.canGoBack()) {
|
|
||||||
webView.goBack();
|
|
||||||
} else {
|
|
||||||
super.onBackPressed();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.example.spaccwebviewapplication;
|
||||||
|
|
||||||
|
import org.eu.spacc.spaccdotweb.android.SpaccWebViewApplication;
|
||||||
|
|
||||||
|
public class MainApplication extends SpaccWebViewApplication {
|
||||||
|
// This proxy class can be left empty,
|
||||||
|
// it exists just to provide an unique android:name for the manifest
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package org.eu.spacc.spaccdotweb.android;
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
public class Constants {
|
public class Constants {
|
||||||
public static enum AppIndex { LOCAL, REMOTE }
|
public enum AppIndex { LOCAL, REMOTE }
|
||||||
|
public enum DataLocation { INTERNAL, EXTERNAL }
|
||||||
}
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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");
|
||||||
|
if (dataLocationSaved == null) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void restartActivity(Context context) {
|
||||||
|
Activity activity = (Activity)context;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
activity.recreate();
|
||||||
|
} else {
|
||||||
|
Intent intent = activity.getIntent();
|
||||||
|
activity.finish();
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public class FileUtils {
|
||||||
|
|
||||||
|
public static void moveDirectory(File sourceLocation, File targetLocation, boolean deleteRoot) throws IOException {
|
||||||
|
copyDirectory(sourceLocation, targetLocation);
|
||||||
|
recursiveDelete(sourceLocation, deleteRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://subversivebytes.wordpress.com/2012/11/05/java-copy-directory-recursive-delete/ */
|
||||||
|
|
||||||
|
public static void copyDirectory(File sourceLocation, File targetLocation) throws IOException {
|
||||||
|
if (sourceLocation.isDirectory()) {
|
||||||
|
if (!targetLocation.exists()) {
|
||||||
|
targetLocation.mkdir();
|
||||||
|
}
|
||||||
|
String[] children = sourceLocation.list();
|
||||||
|
for (int i = 0; i < children.length; i++) {
|
||||||
|
copyDirectory(new File(sourceLocation, children[i]), new File(targetLocation, children[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
InputStream in = new FileInputStream(sourceLocation);
|
||||||
|
OutputStream out = new FileOutputStream(targetLocation);
|
||||||
|
try {
|
||||||
|
byte[] buf = new byte[1024];
|
||||||
|
int len;
|
||||||
|
while((len = in.read(buf)) > 0) {
|
||||||
|
out.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recursiveDelete(File rootDir) {
|
||||||
|
recursiveDelete(rootDir, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void recursiveDelete(File rootDir, boolean deleteRoot) {
|
||||||
|
File[] childDirs = rootDir.listFiles();
|
||||||
|
for (File childDir : childDirs) {
|
||||||
|
if (childDir.isFile()) {
|
||||||
|
childDir.delete();
|
||||||
|
} else {
|
||||||
|
recursiveDelete(childDir, deleteRoot);
|
||||||
|
childDir.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (deleteRoot) {
|
||||||
|
rootDir.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
public class SharedPrefHelper {
|
||||||
|
private final SharedPreferences sharedPref;
|
||||||
|
|
||||||
|
public SharedPrefHelper(Context context) {
|
||||||
|
this.sharedPref = context.getSharedPreferences("SpaccWebView", Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getInt(String name) {
|
||||||
|
Integer value = (Integer)sharedPref.getInt(name, -1);
|
||||||
|
return (value != -1 ? value : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInt(String name, int value) {
|
||||||
|
SharedPreferences.Editor editor = sharedPref.edit().putInt(name, value);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
|
||||||
|
editor.apply();
|
||||||
|
} else {
|
||||||
|
editor.commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
private static final int INPUT_FILE_REQUEST_CODE = 1;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public SpaccWebChromeClient(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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,23 +11,24 @@ public class SpaccWebView extends WebView {
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
public SpaccWebView(Context context, AttributeSet attrs) {
|
public SpaccWebView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
|
this.setWebViewClient(new SpaccWebViewClient(context));
|
||||||
|
this.setWebChromeClient(new SpaccWebChromeClient(context));
|
||||||
|
|
||||||
WebSettings webSettings = getSettings();
|
WebSettings webSettings = this.getSettings();
|
||||||
|
|
||||||
webSettings.setJavaScriptEnabled(Config.ALLOW_JAVASCRIPT);
|
webSettings.setJavaScriptEnabled(Config.ALLOW_JAVASCRIPT);
|
||||||
|
|
||||||
ApiUtils.apiRun(7, () -> webSettings.setDomStorageEnabled(Config.ALLOW_STORAGE));
|
ApiUtils.apiRun(7, () -> webSettings.setDomStorageEnabled(Config.ALLOW_STORAGE));
|
||||||
ApiUtils.apiRun(5, () -> webSettings.setDatabaseEnabled(Config.ALLOW_STORAGE));
|
ApiUtils.apiRun(5, () -> webSettings.setDatabaseEnabled(Config.ALLOW_STORAGE));
|
||||||
if (Config.ALLOW_STORAGE) {
|
if (Config.ALLOW_STORAGE) {
|
||||||
ApiUtils.apiRun(5, () -> webSettings.setDatabasePath(context.getFilesDir().getParent() + "/databases"));
|
ApiUtils.apiRun(5, () -> webSettings.setDatabasePath(context.getDir("databases", 0).getAbsolutePath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiUtils.apiRun(3, () -> webSettings.setAllowFileAccess(true));
|
ApiUtils.apiRun(3, () -> webSettings.setAllowFileAccess(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadAppIndex() {
|
public void loadAppIndex() {
|
||||||
String url = null;
|
String url = null;
|
||||||
|
|
||||||
switch (Config.APP_INDEX) {
|
switch (Config.APP_INDEX) {
|
||||||
case LOCAL:
|
case LOCAL:
|
||||||
url = ("file:///android_asset/" + Config.LOCAL_INDEX);
|
url = ("file:///android_asset/" + Config.LOCAL_INDEX);
|
||||||
|
@ -36,7 +37,6 @@ public class SpaccWebView extends WebView {
|
||||||
url = Config.REMOTE_INDEX;
|
url = Config.REMOTE_INDEX;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUrl(url);
|
loadUrl(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class SpaccWebViewActivity extends Activity {
|
||||||
|
protected SpaccWebView webView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (this.webView.canGoBack()) {
|
||||||
|
this.webView.goBack();
|
||||||
|
} else {
|
||||||
|
super.onBackPressed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi") // We have our custom implementation
|
||||||
|
@Override
|
||||||
|
public File getDataDir() {
|
||||||
|
return getApplicationContext().getDataDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getDir(String name, int mode) {
|
||||||
|
return getApplicationContext().getDir(name, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getFilesDir() {
|
||||||
|
return getApplicationContext().getFilesDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getCacheDir() {
|
||||||
|
return getApplicationContext().getCacheDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getDatabasePath(String name) {
|
||||||
|
return getApplicationContext().getDatabasePath(name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class SpaccWebViewApplication extends Application {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getDataDir() {
|
||||||
|
return StorageUtils.getDataDir(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getDir(String name, int mode) {
|
||||||
|
File dir = new File(getDataDir(), name);
|
||||||
|
dir.mkdirs();
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getFilesDir() {
|
||||||
|
return getDir("files", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getCacheDir() {
|
||||||
|
return getDir("cache", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getDatabasePath(String name) {
|
||||||
|
// TODO: should this be "app_databases"?
|
||||||
|
return new File(getDir("databases", 0), name);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
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<String> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.eu.spacc.spaccdotweb.android;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
public static boolean isInstalledOnExternalStorage(Context context) {
|
||||||
|
try {
|
||||||
|
int flags = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).applicationInfo.flags;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
||||||
|
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/pm/ApplicationInfo.java#2516
|
||||||
|
return ((flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0);
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getProtectedDataDir(Context context) {
|
||||||
|
// Usually is /data/data/<package>
|
||||||
|
return new File(Environment.getDataDirectory() + File.separator + "data" + File.separator + context.getPackageName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getInternalDataDir(Context context) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
|
||||||
|
// Usually is /sdcard/Android/data/<package>
|
||||||
|
return getParentDir(context.getExternalFilesDir(null));
|
||||||
|
} else {
|
||||||
|
// TODO: This can actually be external storage on old Androids, we should make it return null in those cases
|
||||||
|
return new File(Environment.getExternalStorageDirectory() + "Android" + File.separator + "data" + File.separator + context.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getExternalDataDir(Context context) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
||||||
|
File[] dirs = context.getExternalFilesDirs(null);
|
||||||
|
if (dirs.length >= 2) {
|
||||||
|
return getParentDir(dirs[1]);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: We need hacks for old Android systems which have emulated internal + real external storages
|
||||||
|
return getInternalDataDir(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This should not suggest to use external storage if we don't have the necessary manifest permission
|
||||||
|
public static File getDataDir(Context context) {
|
||||||
|
File dir = null;
|
||||||
|
if (isInstalledOnExternalStorage(context)) {
|
||||||
|
dir = getExternalDataDir(context);
|
||||||
|
}
|
||||||
|
if (dir == null) {
|
||||||
|
dir = getProtectedDataDir(context);
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File dataDirFromEnum(Context context, DataLocation dataLocation) {
|
||||||
|
switch (dataLocation) {
|
||||||
|
case INTERNAL:
|
||||||
|
return getProtectedDataDir(context);
|
||||||
|
case EXTERNAL:
|
||||||
|
return getExternalDataDir(context);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getParentDir(File path) {
|
||||||
|
return (path != null ? path.getParentFile() : null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="exit">Esci</string>
|
||||||
|
<string name="move_app_data">Sposta Dati App</string>
|
||||||
|
<string name="move_app_data_info">La app è stata trasferita su una diversa locazione di archiviazione. I dati saranno spostati ora.</string>
|
||||||
|
</resources>
|
|
@ -1,3 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">SpaccWebView Application</string>
|
<string name="app_name" translatable="false">SpaccWebView Application</string>
|
||||||
|
<string name="exit">Exit</string>
|
||||||
|
<string name="move_app_data">Move App Data</string>
|
||||||
|
<string name="move_app_data_info">The app has been transferred to a different storage location. The data will be moved now.</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue