diff --git a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java index 960eb65..8e1a416 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -97,6 +97,7 @@ import acr.browser.lightning.browser.BrowserView; import acr.browser.lightning.browser.TabsView; 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.controller.UIController; import acr.browser.lightning.database.HistoryItem; @@ -800,6 +801,9 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements case R.id.action_history: openHistory(); return true; + case R.id.action_downloads: + openDownloads(); + return true; case R.id.action_add_bookmark: if (currentUrl != null && !UrlUtils.isSpecialUrl(currentUrl)) { addBookmark(currentView.getTitle(), currentUrl); @@ -1598,6 +1602,22 @@ public abstract class BrowserActivity extends ThemableBrowserActivity implements }); } + private void openDownloads() { + new DownloadsPage().getDownloadsPage() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable String item) { + Preconditions.checkNonNull(item); + LightningView view = mTabsManager.getCurrentTab(); + if (view != null) { + view.loadUrl(item); + } + } + }); + } + private View getBookmarkDrawer() { return mSwapBookmarksAndTabs ? mDrawerLeft : mDrawerRight; } diff --git a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java index c8b4945..11ce58c 100644 --- a/app/src/main/java/acr/browser/lightning/activity/TabsManager.java +++ b/app/src/main/java/acr/browser/lightning/activity/TabsManager.java @@ -33,6 +33,7 @@ import acr.browser.lightning.R; 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.dialog.BrowserDialog; @@ -171,6 +172,17 @@ public class TabsManager { tab.loadUrl(item); } }); + } else if (UrlUtils.isDownloadsUrl(url)) { + new DownloadsPage().getDownloadsPage() + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable String item) { + Preconditions.checkNonNull(item); + tab.loadUrl(item); + } + }); } else if (UrlUtils.isStartPageUrl(url)) { new StartPage().getHomepage() .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/acr/browser/lightning/app/AppComponent.java b/app/src/main/java/acr/browser/lightning/app/AppComponent.java index 6605da1..8ca9d5c 100644 --- a/app/src/main/java/acr/browser/lightning/app/AppComponent.java +++ b/app/src/main/java/acr/browser/lightning/app/AppComponent.java @@ -9,6 +9,7 @@ import acr.browser.lightning.activity.ThemableBrowserActivity; import acr.browser.lightning.activity.ThemableSettingsActivity; import acr.browser.lightning.browser.BrowserPresenter; import acr.browser.lightning.constant.BookmarkPage; +import acr.browser.lightning.constant.DownloadsPage; import acr.browser.lightning.constant.HistoryPage; import acr.browser.lightning.constant.StartPage; import acr.browser.lightning.database.history.HistoryDatabase; @@ -67,6 +68,8 @@ public interface AppComponent { void inject(BookmarkPage bookmarkPage); + void inject(DownloadsPage downloadsPage); + void inject(BrowserPresenter presenter); void inject(TabsManager manager); diff --git a/app/src/main/java/acr/browser/lightning/app/AppModule.java b/app/src/main/java/acr/browser/lightning/app/AppModule.java index d4c68f8..400d783 100644 --- a/app/src/main/java/acr/browser/lightning/app/AppModule.java +++ b/app/src/main/java/acr/browser/lightning/app/AppModule.java @@ -10,6 +10,8 @@ import javax.inject.Singleton; import acr.browser.lightning.database.bookmark.BookmarkDatabase; import acr.browser.lightning.database.bookmark.BookmarkModel; +import acr.browser.lightning.database.downloads.DownloadsDatabase; +import acr.browser.lightning.database.downloads.DownloadsModel; import dagger.Module; import dagger.Provides; @@ -38,6 +40,13 @@ public class AppModule { return new BookmarkDatabase(mApp); } + @NonNull + @Provides + @Singleton + public DownloadsModel provideDownloadsMode() { + return new DownloadsDatabase(mApp); + } + @NonNull @Provides @Singleton diff --git a/app/src/main/java/acr/browser/lightning/constant/DownloadsPage.java b/app/src/main/java/acr/browser/lightning/constant/DownloadsPage.java new file mode 100644 index 0000000..8b701d8 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/constant/DownloadsPage.java @@ -0,0 +1,131 @@ +/* + * Copyright 2014 A.C.R. Development + */ +package acr.browser.lightning.constant; + +import android.app.Application; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.anthonycr.bonsai.Single; +import com.anthonycr.bonsai.SingleAction; +import com.anthonycr.bonsai.SingleOnSubscribe; +import com.anthonycr.bonsai.SingleSubscriber; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +import javax.inject.Inject; + +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.utils.Preconditions; +import acr.browser.lightning.utils.Utils; + +public final class DownloadsPage { + + /** + * The download page standard suffix + */ + public static final String FILENAME = "downloads.html"; + + private static final String HEADING_1 = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + private static final String HEADING_2 = "\n" + + "\n" + + "\n" + + "
"; + + private static final String PART1 = "
\n" + + "
\n" + + "
\n" + + "

"; + + private static final String PART3 = "

\n

"; + + private static final String PART4 = "

"; + + private static final String END = "
"; + + private File mFilesDir; + + @Inject Application mApp; + @Inject DownloadsModel mManager; + + @NonNull private final String mTitle; + + public DownloadsPage() { + BrowserApp.getAppComponent().inject(this); + mTitle = mApp.getString(R.string.action_downloads); + } + + @NonNull + public Single getDownloadsPage() { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + mFilesDir = mApp.getFilesDir(); + + buildDownloadsPage(null); + + File downloadsWebPage = new File(mFilesDir, FILENAME); + + subscriber.onItem(Constants.FILE + downloadsWebPage); + subscriber.onComplete(); + } + }); + } + + private void buildDownloadsPage(@Nullable final String folder) { + mManager.getAllDownloads() + .subscribe(new SingleOnSubscribe>() { + @Override + public void onItem(@Nullable List list) { + Preconditions.checkNonNull(list); + + final File downloadsWebPage; + if (folder == null || folder.isEmpty()) { + downloadsWebPage = new File(mFilesDir, FILENAME); + } else { + downloadsWebPage = new File(mFilesDir, folder + '-' + FILENAME); + } + final StringBuilder downloadsBuilder = new StringBuilder(HEADING_1 + mTitle + HEADING_2); + + for (int n = 0, size = list.size(); n < size; n++) { + final DownloadItem item = list.get(n); + downloadsBuilder.append(PART1); + downloadsBuilder.append(item.getUrl()); + downloadsBuilder.append(PART2); + downloadsBuilder.append(item.getTitle()); + downloadsBuilder.append(PART3); + downloadsBuilder.append(item.getContentSize()); + downloadsBuilder.append(PART4); + } + downloadsBuilder.append(END); + FileWriter bookWriter = null; + try { + //noinspection IOResourceOpenedButNotSafelyClosed + bookWriter = new FileWriter(downloadsWebPage, false); + bookWriter.write(downloadsBuilder.toString()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + Utils.close(bookWriter); + } + } + }); + } + +} diff --git a/app/src/main/java/acr/browser/lightning/database/downloads/DownloadItem.java b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadItem.java new file mode 100644 index 0000000..884e052 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadItem.java @@ -0,0 +1,96 @@ +/* + * Copyright 2014 A.C.R. Development + */ +package acr.browser.lightning.database.downloads; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import acr.browser.lightning.utils.Preconditions; + +public class DownloadItem implements Comparable { + + // private variables + @NonNull + private String mUrl = ""; + + @NonNull + private String mTitle = ""; + + @NonNull + private String mContentSize = ""; + + public DownloadItem() {} + + public DownloadItem(@NonNull String url, @NonNull String title, @NonNull String size) { + Preconditions.checkNonNull(url); + Preconditions.checkNonNull(title); + this.mUrl = url; + this.mTitle = title; + this.mContentSize = size; + } + + @NonNull + public String getUrl() { + return this.mUrl; + } + + public void setUrl(@Nullable String url) { + this.mUrl = (url == null) ? "" : url; + } + + @NonNull + public String getTitle() { + return this.mTitle; + } + + public void setTitle(@Nullable String title) { + this.mTitle = (title == null) ? "" : title; + } + + @NonNull + public String getContentSize() { + return this.mContentSize; + } + + public void setContentSize(@Nullable String size) { + this.mContentSize = (size == null) ? "" : size; + } + + @NonNull + @Override + public String toString() { + return mTitle; + } + + @Override + public int compareTo(@NonNull DownloadItem another) { + int compare = this.mTitle.compareTo(another.mTitle); + if (compare == 0) { + return this.mUrl.compareTo(another.mUrl); + } + return compare; + } + + @Override + public boolean equals(@Nullable Object object) { + + if (this == object) return true; + if (object == null) return false; + if (!(object instanceof DownloadItem)) return false; + + DownloadItem that = (DownloadItem) object; + + return this.mTitle.equals(that.mTitle) && this.mUrl.equals(that.mUrl) + && this.mContentSize.equals(that.mContentSize); + } + + @Override + public int hashCode() { + + int result = mUrl.hashCode(); + result = 31 * result + mTitle.hashCode(); + + return result; + } +} diff --git a/app/src/main/java/acr/browser/lightning/database/downloads/DownloadsDatabase.java b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadsDatabase.java new file mode 100644 index 0000000..db2f83c --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/database/downloads/DownloadsDatabase.java @@ -0,0 +1,261 @@ +package acr.browser.lightning.database.downloads; + +import android.app.Application; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; + +import com.anthonycr.bonsai.Completable; +import com.anthonycr.bonsai.CompletableAction; +import com.anthonycr.bonsai.CompletableSubscriber; +import com.anthonycr.bonsai.Single; +import com.anthonycr.bonsai.SingleAction; +import com.anthonycr.bonsai.SingleSubscriber; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import acr.browser.lightning.R; + +/** + * The disk backed download database. + * See {@link DownloadsModel} for method + * documentation. + */ +@Singleton +public class DownloadsDatabase extends SQLiteOpenHelper implements DownloadsModel { + + private static final String TAG = "DownloadsDatabase"; + + // Database Version + private static final int DATABASE_VERSION = 1; + + // Database Name + private static final String DATABASE_NAME = "downloadManager"; + + // HistoryItems table name + private static final String TABLE_DOWNLOADS = "download"; + + // HistoryItems Table Columns names + private static final String KEY_ID = "id"; + private static final String KEY_URL = "url"; + private static final String KEY_TITLE = "title"; + private static final String KEY_SIZE = "size"; + + @NonNull private final String DEFAULT_DOWNLOADS_TITLE; + + @Nullable private SQLiteDatabase mDatabase; + + @Inject + public DownloadsDatabase(@NonNull Application application) { + super(application, DATABASE_NAME, null, DATABASE_VERSION); + DEFAULT_DOWNLOADS_TITLE = application.getString(R.string.untitled); + } + + /** + * Lazily initializes the database + * field when called. + * + * @return a non null writable database. + */ + @WorkerThread + @NonNull + private SQLiteDatabase lazyDatabase() { + if (mDatabase == null || !mDatabase.isOpen()) { + mDatabase = getWritableDatabase(); + } + + return mDatabase; + } + + // Creating Tables + @Override + public void onCreate(@NonNull SQLiteDatabase db) { + String CREATE_BOOKMARK_TABLE = "CREATE TABLE " + + DatabaseUtils.sqlEscapeString(TABLE_DOWNLOADS) + '(' + + DatabaseUtils.sqlEscapeString(KEY_ID) + " INTEGER PRIMARY KEY," + + DatabaseUtils.sqlEscapeString(KEY_URL) + " TEXT," + + DatabaseUtils.sqlEscapeString(KEY_TITLE) + " TEXT," + + DatabaseUtils.sqlEscapeString(KEY_SIZE) + " TEXT" + ')'; + db.execSQL(CREATE_BOOKMARK_TABLE); + } + + // Upgrading database + @Override + public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { + // Drop older table if it exists + db.execSQL("DROP TABLE IF EXISTS " + DatabaseUtils.sqlEscapeString(TABLE_DOWNLOADS)); + // Create tables again + onCreate(db); + } + + @NonNull + private static ContentValues bindBookmarkToContentValues(@NonNull DownloadItem downloadItem) { + ContentValues contentValues = new ContentValues(3); + contentValues.put(KEY_TITLE, downloadItem.getTitle()); + contentValues.put(KEY_URL, downloadItem.getUrl()); + contentValues.put(KEY_SIZE, downloadItem.getContentSize()); + + return contentValues; + } + + @NonNull + private static DownloadItem bindCursorToDownloadItem(@NonNull Cursor cursor) { + DownloadItem download = new DownloadItem(); + + download.setUrl(cursor.getString(cursor.getColumnIndex(KEY_URL))); + download.setTitle(cursor.getString(cursor.getColumnIndex(KEY_TITLE))); + download.setContentSize(cursor.getString(cursor.getColumnIndex(KEY_SIZE))); + + return download; + } + + @NonNull + private static List bindCursorToDownloadItemList(@NonNull Cursor cursor) { + List downloads = new ArrayList<>(); + + while (cursor.moveToNext()) { + downloads.add(bindCursorToDownloadItem(cursor)); + } + + cursor.close(); + + return downloads; + } + + @NonNull + @Override + public Single findDownloadForUrl(@NonNull final String url) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + Cursor cursor = lazyDatabase().query(TABLE_DOWNLOADS, null, KEY_URL + "=?", new String[]{url}, null, null, "1"); + + if (cursor.moveToFirst()) { + subscriber.onItem(bindCursorToDownloadItem(cursor)); + } else { + subscriber.onItem(null); + } + + cursor.close(); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single isDownload(@NonNull final String url) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + Cursor cursor = lazyDatabase().query(TABLE_DOWNLOADS, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1"); + + subscriber.onItem(cursor.moveToFirst()); + + cursor.close(); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single addDownloadIfNotExists(@NonNull final DownloadItem item) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + Cursor cursor = lazyDatabase().query(TABLE_DOWNLOADS, null, KEY_URL + "=?", new String[]{item.getUrl()}, null, null, "1"); + + if (cursor.moveToFirst()) { + cursor.close(); + subscriber.onItem(false); + subscriber.onComplete(); + return; + } + + cursor.close(); + + long id = lazyDatabase().insert(TABLE_DOWNLOADS, null, bindBookmarkToContentValues(item)); + + subscriber.onItem(id != -1); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Completable addDownloadsList(@NonNull final List bookmarkItems) { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + lazyDatabase().beginTransaction(); + + for (DownloadItem item : bookmarkItems) { + addDownloadIfNotExists(item).subscribe(); + } + + lazyDatabase().setTransactionSuccessful(); + lazyDatabase().endTransaction(); + + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single deleteDownload(@NonNull final DownloadItem bookmark) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + int rows = lazyDatabase().delete(TABLE_DOWNLOADS, KEY_URL + "=?", new String[]{bookmark.getUrl()}); + + subscriber.onItem(rows > 0); + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Completable deleteAllDownloads() { + return Completable.create(new CompletableAction() { + @Override + public void onSubscribe(@NonNull CompletableSubscriber subscriber) { + lazyDatabase().delete(TABLE_DOWNLOADS, null, null); + + subscriber.onComplete(); + } + }); + } + + @NonNull + @Override + public Single> getAllDownloads() { + return Single.create(new SingleAction>() { + @Override + public void onSubscribe(@NonNull SingleSubscriber> 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..7ce5f3e --- /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 anthonycr on 5/6/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 download the download to delete. + * @return an observable that emits true when + * the download is deleted, false otherwise. + */ + @NonNull + Single deleteDownload(@NonNull DownloadItem download); + + /** + * 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/download/LightningDownloadListener.java b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java index 31ed7cb..fba4cba 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; @@ -75,6 +81,16 @@ public class LightningDownloadListener implements DownloadListener { dialogClickListener).show(); BrowserDialog.setDialogSize(mActivity, dialog); Log.i(TAG, "Downloading: " + fileName); + + downloadsModel.addDownloadIfNotExists(new DownloadItem(url, fileName, downloadSize)).subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Boolean item) { + super.onItem(item); + + if (item != null && !item) + Log.i(TAG, "error saving download to database"); + } + }); } @Override 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/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..7479d79 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