> subscriber) {
+ Cursor cursor = lazyDatabase().query(TABLE_DOWNLOADS, null, null, null, null, null, null);
+
+ subscriber.onItem(bindCursorToDownloadItemList(cursor));
+ subscriber.onComplete();
+ }
+ });
+ }
+
+ @Override
+ public long count() {
+ return DatabaseUtils.queryNumEntries(lazyDatabase(), TABLE_DOWNLOADS);
+ }
+
+}
diff --git a/app/src/main/java/acr/browser/lightning/database/downloads/DownloadsModel.java b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadsModel.java
new file mode 100644
index 0000000..a5545ef
--- /dev/null
+++ b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadsModel.java
@@ -0,0 +1,97 @@
+package acr.browser.lightning.database.downloads;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+
+import com.anthonycr.bonsai.Completable;
+import com.anthonycr.bonsai.Single;
+
+import java.util.List;
+
+/**
+ * The interface that should be used to
+ * communicate with the download database.
+ *
+ * Created by df1e on 29/5/17.
+ */
+public interface DownloadsModel {
+
+ /**
+ * Determines if a URL is associated with a download.
+ *
+ * @param url the URL to check.
+ * @return an observable that will emit true if
+ * the URL is a download, false otherwise.
+ */
+ @NonNull
+ Single isDownload(@NonNull String url);
+
+ /**
+ * Gets the download associated with the URL.
+ *
+ * @param url the URL to look for.
+ * @return an observable that will emit either
+ * the download associated with the URL or null.
+ */
+ @NonNull
+ Single findDownloadForUrl(@NonNull String url);
+
+ /**
+ * Adds a download if one does not already exist with
+ * the same URL.
+ *
+ * @param item the download to add.
+ * @return an observable that emits true if the download
+ * was added, false otherwise.
+ */
+ @NonNull
+ Single addDownloadIfNotExists(@NonNull DownloadItem item);
+
+ /**
+ * Adds a list of downloads to the database.
+ *
+ * @param downloadItems the downloads to add.
+ * @return an observable that emits a complete event
+ * when all the downloads have been added.
+ */
+ @NonNull
+ Completable addDownloadsList(@NonNull List downloadItems);
+
+ /**
+ * Deletes a download from the database.
+ *
+ * @param url the download url to delete.
+ * @return an observable that emits true when
+ * the download is deleted, false otherwise.
+ */
+ @NonNull
+ Single deleteDownload(@NonNull String url);
+
+ /**
+ * Deletes all downloads in the database.
+ *
+ * @return an observable that emits a completion
+ * event when all downloads have been deleted.
+ */
+ @NonNull
+ Completable deleteAllDownloads();
+
+ /**
+ * Emits a list of all downloads
+ *
+ * @return an observable that emits a list
+ * of all downloads.
+ */
+ @NonNull
+ Single> getAllDownloads();
+
+ /**
+ * A synchronous call to the model
+ * that returns the number of downloads.
+ * Should be called from a background thread.
+ *
+ * @return the number of downloads in the database.
+ */
+ @WorkerThread
+ long count();
+}
diff --git a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java
index 490a9f8..9763383 100644
--- a/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java
+++ b/app/src/main/java/acr/browser/lightning/dialog/LightningDialogBuilder.java
@@ -8,12 +8,15 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.text.TextUtils;
+import android.util.Log;
import android.view.View;
+import android.webkit.URLUtil;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import com.anthonycr.bonsai.CompletableOnSubscribe;
+import com.anthonycr.bonsai.CompletableSubscriber;
import com.anthonycr.bonsai.Schedulers;
import com.anthonycr.bonsai.SingleOnSubscribe;
@@ -29,6 +32,8 @@ import acr.browser.lightning.constant.Constants;
import acr.browser.lightning.controller.UIController;
import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkModel;
+import acr.browser.lightning.database.downloads.DownloadItem;
+import acr.browser.lightning.database.downloads.DownloadsModel;
import acr.browser.lightning.database.history.HistoryModel;
import acr.browser.lightning.preference.PreferenceManager;
import acr.browser.lightning.utils.IntentUtils;
@@ -41,6 +46,7 @@ import acr.browser.lightning.utils.Utils;
* Created by Stefano Pacifici on 02/09/15, based on Anthony C. Restaino's code.
*/
public class LightningDialogBuilder {
+ public static final String TAG = "LightningDialogBuilder";
public enum NewTab {
FOREGROUND,
@@ -49,6 +55,7 @@ public class LightningDialogBuilder {
}
@Inject BookmarkModel mBookmarkManager;
+ @Inject DownloadsModel mDownloadsModel;
@Inject PreferenceManager mPreferenceManager;
@Inject
@@ -152,6 +159,33 @@ public class LightningDialogBuilder {
});
}
+ /**
+ * Show the appropriated dialog for the long pressed link.
+ *
+ * @param activity used to show the dialog
+ * @param url the long pressed url
+ */
+ public void showLongPressedDialogForDownloadUrl(@NonNull final Activity activity,
+ @NonNull final UIController uiController,
+ @NonNull final String url) {
+
+ BrowserDialog.show(activity, R.string.action_downloads,
+ new BrowserDialog.Item(R.string.dialog_delete_all_downloads) {
+ @Override
+ public void onClick() {
+ mDownloadsModel.deleteAllDownloads()
+ .subscribeOn(Schedulers.io())
+ .observeOn(Schedulers.main())
+ .subscribe(new CompletableOnSubscribe() {
+ @Override
+ public void onComplete() {
+ uiController.handleDownloadDeleted();
+ }
+ });
+ }
+ });
+ }
+
private void showEditBookmarkDialog(@NonNull final Activity activity,
@NonNull final UIController uiController,
@NonNull final HistoryItem item) {
@@ -352,6 +386,15 @@ public class LightningDialogBuilder {
@Override
public void onClick() {
Utils.downloadFile(activity, mPreferenceManager, url, userAgent, "attachment");
+
+ mDownloadsModel.addDownloadIfNotExists(new DownloadItem(url, URLUtil.guessFileName(url, null, null), ""))
+ .subscribe(new SingleOnSubscribe() {
+ @Override
+ public void onItem(@Nullable Boolean item) {
+ if (item != null && !item)
+ Log.i(TAG, "error saving download to database");
+ }
+ });
}
});
}
diff --git a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java
index 7fdab9b..6f02b92 100644
--- a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java
+++ b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java
@@ -47,15 +47,6 @@ public class DownloadHandler {
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.getPath();
- @Nullable
- static String guessFileExtension(@NonNull String filename) {
- int lastIndex = filename.lastIndexOf('.') + 1;
- if (lastIndex > 0 && filename.length() > lastIndex) {
- return filename.substring(lastIndex, filename.length());
- }
- return null;
- }
-
/**
* Notify the host application a download should be done, or that the data
* should be streamed if a streaming viewer is available.
@@ -222,7 +213,7 @@ public class DownloadHandler {
Utils.showSnackbar(context, R.string.problem_location_download);
return;
}
- String newMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(guessFileExtension(filename));
+ String newMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(Utils.guessFileExtension(filename));
Log.d(TAG, "New mimetype: " + newMimeType);
request.setMimeType(newMimeType);
request.setDestinationUri(Uri.parse(Constants.FILE + location + filename));
diff --git a/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java b/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java
index fcef5ce..96b7078 100644
--- a/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java
+++ b/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java
@@ -93,7 +93,7 @@ class FetchUrlMimeType extends Thread {
if (mimeType.equalsIgnoreCase("text/plain")
|| mimeType.equalsIgnoreCase("application/octet-stream")) {
String newMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
- DownloadHandler.guessFileExtension(mUri));
+ Utils.guessFileExtension(mUri));
if (newMimeType != null) {
mRequest.setMimeType(newMimeType);
}
diff --git a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java
index 31ed7cb..a38bb93 100644
--- a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java
+++ b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java
@@ -7,6 +7,7 @@ import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
+import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.text.format.Formatter;
import android.util.Log;
@@ -15,9 +16,12 @@ import android.webkit.URLUtil;
import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
+import acr.browser.lightning.database.downloads.DownloadItem;
+import acr.browser.lightning.database.downloads.DownloadsModel;
import acr.browser.lightning.dialog.BrowserDialog;
import acr.browser.lightning.preference.PreferenceManager;
+import com.anthonycr.bonsai.SingleOnSubscribe;
import com.anthonycr.grant.PermissionsManager;
import com.anthonycr.grant.PermissionsResultAction;
@@ -31,6 +35,8 @@ public class LightningDownloadListener implements DownloadListener {
@Inject PreferenceManager mPreferenceManager;
+ @Inject DownloadsModel downloadsModel;
+
public LightningDownloadListener(Activity context) {
BrowserApp.getAppComponent().inject(this);
mActivity = context;
@@ -44,7 +50,15 @@ public class LightningDownloadListener implements DownloadListener {
new PermissionsResultAction() {
@Override
public void onGranted() {
- String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
+ final String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype);
+ final String downloadSize;
+
+ if (contentLength > 0) {
+ downloadSize = Formatter.formatFileSize(mActivity, contentLength);
+ } else {
+ downloadSize = mActivity.getString(R.string.unknown_size);
+ }
+
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -52,6 +66,15 @@ public class LightningDownloadListener implements DownloadListener {
case DialogInterface.BUTTON_POSITIVE:
DownloadHandler.onDownloadStart(mActivity, mPreferenceManager, url, userAgent,
contentDisposition, mimetype);
+
+ downloadsModel.addDownloadIfNotExists(new DownloadItem(url, fileName, downloadSize))
+ .subscribe(new SingleOnSubscribe() {
+ @Override
+ public void onItem(@Nullable Boolean item) {
+ if (item != null && !item)
+ Log.i(TAG, "error saving download to database");
+ }
+ });
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
@@ -60,12 +83,6 @@ public class LightningDownloadListener implements DownloadListener {
};
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); // dialog
- String downloadSize;
- if (contentLength > 0) {
- downloadSize = Formatter.formatFileSize(mActivity, contentLength);
- } else {
- downloadSize = mActivity.getString(R.string.unknown_size);
- }
String message = mActivity.getString(R.string.dialog_download, downloadSize);
Dialog dialog = builder.setTitle(fileName)
.setMessage(message)
diff --git a/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java
index 63ba798..654e417 100644
--- a/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java
+++ b/app/src/main/java/acr/browser/lightning/utils/UrlUtils.java
@@ -25,6 +25,7 @@ import java.util.regex.Pattern;
import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.constant.Constants;
+import acr.browser.lightning.constant.DownloadsPage;
import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.constant.StartPage;
@@ -91,6 +92,7 @@ public class UrlUtils {
public static boolean isSpecialUrl(@Nullable String url) {
return url != null && url.startsWith(Constants.FILE) &&
(url.endsWith(BookmarkPage.FILENAME) ||
+ url.endsWith(DownloadsPage.FILENAME) ||
url.endsWith(HistoryPage.FILENAME) ||
url.endsWith(StartPage.FILENAME));
}
@@ -105,6 +107,16 @@ public class UrlUtils {
return url != null && url.startsWith(Constants.FILE) && url.endsWith(BookmarkPage.FILENAME);
}
+ /**
+ * Determines if the url is a url for the bookmark page.
+ *
+ * @param url the url to check, may be null.
+ * @return true if the url is a bookmark url, false otherwise.
+ */
+ public static boolean isDownloadsUrl(@Nullable String url) {
+ return url != null && url.startsWith(Constants.FILE) && url.endsWith(DownloadsPage.FILENAME);
+ }
+
/**
* Determines if the url is a url for the history page.
*
diff --git a/app/src/main/java/acr/browser/lightning/utils/Utils.java b/app/src/main/java/acr/browser/lightning/utils/Utils.java
index 62a2669..0bb7682 100644
--- a/app/src/main/java/acr/browser/lightning/utils/Utils.java
+++ b/app/src/main/java/acr/browser/lightning/utils/Utils.java
@@ -456,4 +456,13 @@ public final class Utils {
return inSampleSize;
}
+ @Nullable
+ public static String guessFileExtension(@NonNull String filename) {
+ int lastIndex = filename.lastIndexOf('.') + 1;
+ if (lastIndex > 0 && filename.length() > lastIndex) {
+ return filename.substring(lastIndex, filename.length());
+ }
+ return null;
+ }
+
}
diff --git a/app/src/main/java/acr/browser/lightning/view/LightningView.java b/app/src/main/java/acr/browser/lightning/view/LightningView.java
index 1f137f0..8532dea 100644
--- a/app/src/main/java/acr/browser/lightning/view/LightningView.java
+++ b/app/src/main/java/acr/browser/lightning/view/LightningView.java
@@ -47,6 +47,7 @@ import javax.inject.Inject;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.constant.BookmarkPage;
import acr.browser.lightning.constant.Constants;
+import acr.browser.lightning.constant.DownloadsPage;
import acr.browser.lightning.constant.HistoryPage;
import acr.browser.lightning.constant.StartPage;
import acr.browser.lightning.controller.UIController;
@@ -242,6 +243,24 @@ public class LightningView {
});
}
+ /**
+ * This method gets the bookmark page URL from the {@link BookmarkPage}
+ * class asynchronously and loads the URL in the WebView on the
+ * UI thread. It also caches the default folder icon locally.
+ */
+ public void loadDownloadspage() {
+ new DownloadsPage().getDownloadsPage()
+ .subscribeOn(Schedulers.io())
+ .observeOn(Schedulers.main())
+ .subscribe(new SingleOnSubscribe() {
+ @Override
+ public void onItem(@Nullable String item) {
+ Preconditions.checkNonNull(item);
+ loadUrl(item);
+ }
+ });
+ }
+
/**
* Initialize the preference driven settings of the WebView. This method
* must be called whenever the preferences are changed within SharedPreferences.
@@ -984,6 +1003,13 @@ public class LightningView {
final String newUrl = result.getExtra();
mBookmarksDialogBuilder.showLongPressedDialogForBookmarkUrl(mActivity, mUIController, newUrl);
}
+ } else if (currentUrl.endsWith(DownloadsPage.FILENAME)) {
+ if (url != null) {
+ mBookmarksDialogBuilder.showLongPressedDialogForDownloadUrl(mActivity, mUIController, url);
+ } else if (result != null && result.getExtra() != null) {
+ final String newUrl = result.getExtra();
+ mBookmarksDialogBuilder.showLongPressedDialogForDownloadUrl(mActivity, mUIController, newUrl);
+ }
}
} else {
if (url != null) {
diff --git a/app/src/main/java/acr/browser/lightning/view/LightningWebClient.java b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.java
index 2faf3de..8e3f4bb 100644
--- a/app/src/main/java/acr/browser/lightning/view/LightningWebClient.java
+++ b/app/src/main/java/acr/browser/lightning/view/LightningWebClient.java
@@ -8,16 +8,19 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.MailTo;
+import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.content.FileProvider;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.HttpAuthHandler;
+import android.webkit.MimeTypeMap;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebResourceRequest;
@@ -28,6 +31,7 @@ import android.widget.EditText;
import android.widget.TextView;
import java.io.ByteArrayInputStream;
+import java.io.File;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -35,6 +39,7 @@ import java.util.Map;
import javax.inject.Inject;
+import acr.browser.lightning.BuildConfig;
import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.constant.Constants;
@@ -354,6 +359,26 @@ public class LightningWebClient extends WebViewClient {
}
return true;
}
+ } else if (url.startsWith(Constants.FILE)) {
+ File file = new File(url.replace(Constants.FILE, ""));
+
+ if (file.exists()) {
+ String newMimeType = MimeTypeMap.getSingleton()
+ .getMimeTypeFromExtension(Utils.guessFileExtension(file.toString()));
+
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Uri contentUri = FileProvider.getUriForFile(mActivity, BuildConfig.APPLICATION_ID + ".fileprovider", file);
+ intent.setDataAndType(contentUri, newMimeType);
+
+ try {
+ mActivity.startActivity(intent);
+ } catch (Exception e) {
+ System.out.println("LightningWebClient: cannot open downloaded file");
+ }
+ return true;
+ }
}
return false;
}
diff --git a/app/src/main/res/menu-large/main.xml b/app/src/main/res/menu-large/main.xml
index 92b63e1..958acfa 100644
--- a/app/src/main/res/menu-large/main.xml
+++ b/app/src/main/res/menu-large/main.xml
@@ -47,6 +47,9 @@
+
diff --git a/app/src/main/res/menu-xlarge/main.xml b/app/src/main/res/menu-xlarge/main.xml
index 92b63e1..958acfa 100644
--- a/app/src/main/res/menu-xlarge/main.xml
+++ b/app/src/main/res/menu-xlarge/main.xml
@@ -47,6 +47,9 @@
+
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 9bfa01b..f620979 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -29,6 +29,9 @@
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ee47f61..71a594c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -6,6 +6,7 @@
Share
History
Bookmarks
+ Downloads
Add bookmark
Copy link
Forward
@@ -245,4 +246,6 @@
Rename folder
Remove folder
Close browser
+ Delete download
+ Delete all downloads
diff --git a/app/src/main/res/xml/filepaths.xml b/app/src/main/res/xml/filepaths.xml
new file mode 100644
index 0000000..065dee8
--- /dev/null
+++ b/app/src/main/res/xml/filepaths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file