Initial bookmarks implementation. Fix for Custom User Agent not working. https...
authorSoren Stoutner <soren@stoutner.com>
Thu, 30 Jun 2016 05:02:37 +0000 (22:02 -0700)
committerSoren Stoutner <soren@stoutner.com>
Thu, 30 Jun 2016 05:02:37 +0000 (22:02 -0700)
31 files changed:
.idea/dictionaries/soren.xml
app/src/main/AndroidManifest.xml
app/src/main/assets/about_license.html
app/src/main/assets/guide_clear_and_exit.html
app/src/main/assets/guide_javascript.html
app/src/main/assets/guide_overview.html
app/src/main/assets/guide_planned_features.html
app/src/main/assets/images/advertising_id.png [new file with mode: 0644]
app/src/main/assets/images/ic_bookmark_border.png [new file with mode: 0644]
app/src/main/assets/images/panopticlick.png [new file with mode: 0644]
app/src/main/assets/images/user_agent.png [new file with mode: 0644]
app/src/main/assets/images/webkay.png [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/AboutActivity.java
app/src/main/java/com/stoutner/privacybrowser/BookmarksActivity.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/CreateBookmark.java [new file with mode: 0644]
app/src/main/java/com/stoutner/privacybrowser/CreateHomeScreenShortcut.java
app/src/main/java/com/stoutner/privacybrowser/GuideActivity.java
app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/SettingsFragment.java
app/src/main/res/drawable/add.xml [new file with mode: 0644]
app/src/main/res/drawable/bookmarks.xml [new file with mode: 0644]
app/src/main/res/layout/about_coordinatorlayout.xml
app/src/main/res/layout/bookmarks_coordinatorlayout.xml [new file with mode: 0644]
app/src/main/res/layout/bookmarks_item_linearlayout.xml [new file with mode: 0644]
app/src/main/res/layout/create_bookmark_dialog.xml [new file with mode: 0644]
app/src/main/res/layout/create_home_screen_shortcut_dialog.xml
app/src/main/res/menu/menu_navigation.xml
app/src/main/res/values-v19/styles.xml
app/src/main/res/values/strings.xml
app/src/main/res/values/styles.xml

index 2fcbda7330b49bbe72b750abdc4f90c2d8f2b4fb..f0434fc3586f22b88cc51dbe34a27f00db4fdbc9 100644 (file)
@@ -7,24 +7,32 @@
       <w>appbarlayout</w>
       <w>aren</w>
       <w>bebdb</w>
+      <w>bookmarkname</w>
+      <w>bookmarkurl</w>
       <w>buildapi</w>
       <w>buildversion</w>
       <w>chromeversion</w>
       <w>commitdiff</w>
       <w>coordinatorlayout</w>
+      <w>displayorder</w>
       <w>eadd</w>
+      <w>edittext</w>
       <w>exynos</w>
+      <w>favoriteicon</w>
       <w>imbedded</w>
       <w>imbedding</w>
       <w>intl</w>
+      <w>isfolder</w>
       <w>khtml</w>
       <w>konqueror</w>
       <w>linearlayout</w>
+      <w>listview</w>
       <w>logins</w>
       <w>mozilla</w>
       <w>nojs</w>
       <w>orbot</w>
       <w>panopticlick</w>
+      <w>parentfolder</w>
       <w>redmine</w>
       <w>referer</w>
       <w>relativelayout</w>
@@ -36,6 +44,7 @@
       <w>tablayout</w>
       <w>techrepublic</w>
       <w>textview</w>
+      <w>uids</w>
       <w>webkay</w>
       <w>webkitversion</w>
       <w>whatismyip</w>
index 8bc6746e19ede6beb369f35e1227a32e33745abe..246393e9c4c33fba0431a344e140c0de61eb808c 100644 (file)
             </intent-filter>
         </activity>
 
+
         <!-- android:configChanges="orientation|screenSize" makes the activity not reload when the orientation changes.
           android:persistableMode="persistNever" removes Privacy Browser from the recents screen on a device reboot. -->
         <activity
-            android:name=".GuideActivity"
-            android:label="@string/privacy_browser_guide"
-            android:theme="@style/PrivacyBrowser.TabActivity"
+            android:name=".BookmarksActivity"
+            android:label="@string/bookmarks"
+            android:theme="@style/PrivacyBrowser.SecondaryActivity"
             android:parentActivityName=".MainWebViewActivity"
             android:configChanges="orientation|screenSize"
             android:persistableMode="persistNever" >
             android:persistableMode="persistNever" >
         </activity>
 
+        <!-- android:configChanges="orientation|screenSize" makes the activity not reload when the orientation changes.
+          android:persistableMode="persistNever" removes Privacy Browser from the recents screen on a device reboot. -->
+        <activity
+            android:name=".GuideActivity"
+            android:label="@string/privacy_browser_guide"
+            android:theme="@style/PrivacyBrowser.SecondaryActivity"
+            android:parentActivityName=".MainWebViewActivity"
+            android:configChanges="orientation|screenSize"
+            android:persistableMode="persistNever" >
+        </activity>
+
         <!-- android:configChanges="orientation|screenSize" makes the activity not reload when the orientation changes.
           android:persistableMode="persistNever" removes Privacy Browser from the recents screen on a device reboot. -->
         <activity
             android:name=".AboutActivity"
             android:label="@string/about_privacy_browser"
-            android:theme="@style/PrivacyBrowser.TabActivity"
+            android:theme="@style/PrivacyBrowser.SecondaryActivity"
             android:parentActivityName=".MainWebViewActivity"
             android:configChanges="orientation|screenSize"
             android:persistableMode="persistNever" >
index 094ec166162c513d00bccc14ee14f0232a11c433..6e4dc0a811db85f8c3662c6534108623a0771801 100644 (file)
 
 <p><img class="center" src="images/ic_arrow_forward.png" height="32" width="32"> ic_arrow_forward.</p>
 
-<p><img class="center" src="images/ic_file_download.png" height="32" width="32"> ic_file_download.</p>
+<p><img class="center" src="images/ic_bookmark_border.png" height="32" width="32"> ic_bookmark_border.</p>
 
-<p><img class="center" src="images/ic_import_contacts.png" height="32" width="32"> ic_import_contacts.</p>
+<p><img class="center" src="images/ic_file_download.png" height="32" width="32"> ic_file_download.</p>
 
 <p><img class="center" src="images/ic_settings.png" height="32" width="32"> ic_settings.</p>
 
+<p><img class="center" src="images/ic_import_contacts.png" height="32" width="32"> ic_import_contacts.</p>
+
 <p><img class="center" src="images/ic_info_outline.png" height="32" width="32"> ic_info_outline.</p>
 
 <p><img class="center" src="images/ic_exit_to_app.png" height="32" width="32"> ic_exit_to_app.</p>
 
+<p><img class="center" src="images/ic_add.png" height="32" width="32"> ic_add.</p>
+
 <hr/>
 
 <h3>GNU General Public License</h3>
index 7f29729677c9e91cad91ab452805b32c3f846d51..9d5c1f34e9d5524f73198022fbbc09c80b960bae 100644 (file)
@@ -38,6 +38,7 @@
 <ul>
     <li><item>Removes all cookies</item>.</li>
     <li><item>Removes all DOM storage</item>.</li>
+    <li><item>Removes all form data</item>.</li>
     <li><item>Clears the cache, including disk files</item>.</li>
     <li><item>Clears the back/forward history</item>.</li>
     <li><item>Destroys the internal state of the WebView</item>.</li>
index 1f1bcdc8d43e3a220563f4563e1b92afebb89a37..1cfd92e2c127e2b75bf65d8df7dfdc55cab173b1 100644 (file)
@@ -47,7 +47,7 @@
 <p>Of course, the concept of running arbitrary programs from a website is potentially dangerous.  So there are limitations placed on JavaScript
     to keep it from doing things like installing viruses on the device.  However, it turns out that these limitations are overly broad.
     Below is a screenshot from <a href="http://webkay.robinlinus.com">webkay</a>, which is a website that demonstrates the type of information that
-    JavaScript can produce about a device.</p>
+    JavaScript can produce about a device.  <a href="http://www.browserleaks.com/">Browser Leaks</a> is another good resource.</p>
 
 <p><img class="center" src="images/webkay.png" height="640" width="360"></p>
 
index a1c5701eeaf32dd65f6fbd7d4641b45aa31f490f..90e14d6ab02ea7c4967f7af0eac8c87389bd60f4 100644 (file)
     without informing the user they are doing so.  Privacy Browser is designed to grant the user as much information and control over these tracking techniques as possible.</p>
 
 
-<h3>WebView Limitations</h3>
+<h3>Android's WebView Limitations</h3>
 
 <p>Privacy Browser uses Android's built-in WebView to render websites.  There are some limitations in the controls WebView exposes for managing privacy settings.  For example,
-    it isn't possible to enable some JavaScript commands while disabling others.  It also isn't possible to control the
-    <a href="https://blog.fastmail.com/2016/06/20/everything-you-could-ever-want-to-know-and-more-about-controlling-the-referer-header/">referer header</a>.
-    Once Privacy Browser has matured to take full advantage of all the privacy options WebView does offer, some consideration might be made to imbedding a customized WebView
-    or using a different rendering engine.</p>
+    it isn't possible to enable some JavaScript commands while disabling others. Once Privacy Browser has matured to take full advantage of all the privacy options WebView
+    does offer, some consideration might be made to imbedding a customized WebView or using a different rendering engine.</p>
 </body>
 </html>
\ No newline at end of file
index e4796fd1f9896ff1e0ddf2fe9242c5e4de17d26f..7e8891ead985ff011f183f4c71cf7aef5df18b08 100644 (file)
@@ -43,6 +43,6 @@
     <li><strong>Integrated ad blocker</strong>.</li>
 </ul>
 
-<p>A full list of planned features and bug reports is available on <a href="https://redmine.stoutner.com/projects/privacy-browser/issues">redmine.stoutner.com</a>.</p>
+<p>A full list of planned features and bug reports is available at <a href="https://redmine.stoutner.com/projects/privacy-browser/issues">redmine.stoutner.com</a>.</p>
 </body>
 </html>
\ No newline at end of file
diff --git a/app/src/main/assets/images/advertising_id.png b/app/src/main/assets/images/advertising_id.png
new file mode 100644 (file)
index 0000000..8ebf58e
Binary files /dev/null and b/app/src/main/assets/images/advertising_id.png differ
diff --git a/app/src/main/assets/images/ic_bookmark_border.png b/app/src/main/assets/images/ic_bookmark_border.png
new file mode 100644 (file)
index 0000000..ddc4b3c
Binary files /dev/null and b/app/src/main/assets/images/ic_bookmark_border.png differ
diff --git a/app/src/main/assets/images/panopticlick.png b/app/src/main/assets/images/panopticlick.png
new file mode 100644 (file)
index 0000000..ba9cd5d
Binary files /dev/null and b/app/src/main/assets/images/panopticlick.png differ
diff --git a/app/src/main/assets/images/user_agent.png b/app/src/main/assets/images/user_agent.png
new file mode 100644 (file)
index 0000000..50ac8dc
Binary files /dev/null and b/app/src/main/assets/images/user_agent.png differ
diff --git a/app/src/main/assets/images/webkay.png b/app/src/main/assets/images/webkay.png
new file mode 100644 (file)
index 0000000..09a1b5e
Binary files /dev/null and b/app/src/main/assets/images/webkay.png differ
index 95b5fb338829816c762528fe72c99f8bcb4382ba..62978aac7ebdbfd2832e8dc1953b5ac35cecb896 100644 (file)
@@ -36,8 +36,8 @@ public class AboutActivity extends AppCompatActivity {
         setContentView(R.layout.about_coordinatorlayout);
 
         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
-        Toolbar supportAppBar = (Toolbar) findViewById(R.id.about_toolbar);
-        setSupportActionBar(supportAppBar);
+        Toolbar aboutAppBar = (Toolbar) findViewById(R.id.about_toolbar);
+        setSupportActionBar(aboutAppBar);
 
         // Display the home arrow on supportAppBar.
         final ActionBar appBar = getSupportActionBar();
diff --git a/app/src/main/java/com/stoutner/privacybrowser/BookmarksActivity.java b/app/src/main/java/com/stoutner/privacybrowser/BookmarksActivity.java
new file mode 100644 (file)
index 0000000..6d08199
--- /dev/null
@@ -0,0 +1,157 @@
+/**
+ * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Bundle;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+import java.io.ByteArrayOutputStream;
+
+public class BookmarksActivity extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener {
+    private BookmarksDatabaseHandler bookmarksDatabaseHandler;
+    private ListView bookmarksListView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.bookmarks_coordinatorlayout);
+
+        // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
+        Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.bookmarks_toolbar);
+        setSupportActionBar(bookmarksAppBar);
+
+        // Display the home arrow on supportAppBar.
+        final ActionBar appBar = getSupportActionBar();
+        assert appBar != null;// This assert removes the incorrect warning in Android Studio on the following line that appBar might be null.
+        appBar.setDisplayHomeAsUpEnabled(true);
+
+        // Initialize the database handler and the ListView.
+        bookmarksDatabaseHandler = new BookmarksDatabaseHandler(this, null, null, 0);
+        bookmarksListView = (ListView) findViewById(R.id.bookmarks_listview);
+
+        // Display the bookmarks in the ListView.
+        updateBookmarksListView();
+
+        // Set a listener so that tapping a list item loads the URL.  We need to store the activity in a variable so that we can return to the parent activity after loading the URL.
+        final Activity bookmarksActivity = this;
+        bookmarksListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                // Convert the id from long to int to match the format of the bookmarks database.
+                int databaseID = (int) id;
+
+                // Get the bookmark URL and assign it to formattedUrlString.
+                MainWebViewActivity.formattedUrlString = bookmarksDatabaseHandler.getBookmarkURL(databaseID);
+
+                //  Load formattedUrlString and return to the main activity.
+                MainWebViewActivity.mainWebView.loadUrl(MainWebViewActivity.formattedUrlString);
+                NavUtils.navigateUpFromSameTask(bookmarksActivity);
+            }
+        });
+
+        // Set a FloatingActionButton for creating new bookmarks.
+        FloatingActionButton createBookmarkFAB = (FloatingActionButton) findViewById(R.id.create_bookmark_fab);
+        assert createBookmarkFAB != null;
+        createBookmarkFAB.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                // Show the CreateBookmark AlertDialog and name the instance "@string/create_bookmark".
+                DialogFragment createBookmarkDialog = new CreateBookmark();
+                createBookmarkDialog.show(getFragmentManager(), "@string/create_bookmark");
+            }
+        });
+    }
+
+    @Override
+    public void onCreateBookmarkCancel(DialogFragment createBookmarkDialogFragment) {
+        // Do nothing because the user selected "Cancel".
+    }
+
+    @Override
+    public void onCreateBookmarkCreate(DialogFragment createBookmarkDialogFragment) {
+        // Get the EditTexts from the DialogFragment and extract the strings.
+        EditText createBookmarkNameEditText = (EditText) createBookmarkDialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
+        String bookmarkNameString = createBookmarkNameEditText.getText().toString();
+        EditText createBookmarkURLEditText = (EditText) createBookmarkDialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+        String bookmarkURLString = createBookmarkURLEditText.getText().toString();
+
+        // Convert the favoriteIcon Bitmap to a byte array.
+        ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+        MainWebViewActivity.favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+        byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
+
+        // Create the bookmark.
+        bookmarksDatabaseHandler.createBookmark(bookmarkNameString, bookmarkURLString, favoriteIconByteArray);
+
+        // Refresh the ListView.
+        updateBookmarksListView();
+    }
+
+    private void updateBookmarksListView() {
+        // Get a Cursor with the current contents of the bookmarks database.
+        final Cursor bookmarksCursor = bookmarksDatabaseHandler.getBookmarksCursor();
+
+        // The last argument is 0 because no special behavior is required.
+        SimpleCursorAdapter bookmarksAdapter = new SimpleCursorAdapter(this,
+                R.layout.bookmarks_item_linearlayout,
+                bookmarksCursor,
+                new String[] { BookmarksDatabaseHandler.FAVORITEICON, BookmarksDatabaseHandler.BOOKMARK_NAME },
+                new int[] { R.id.bookmark_favorite_icon, R.id.bookmark_name },
+                0);
+
+        // Override the handling of R.id.bookmark_favorite_icon to load an image instead of a string.
+        bookmarksAdapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() {
+            public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
+                if (view.getId() == R.id.bookmark_favorite_icon) {
+                    // Get the byte array from the database.
+                    byte[] favoriteIconByteArray = cursor.getBlob(columnIndex);
+
+                    // Convert the byte array to a Bitmap beginning at the first byte and ending at the last.
+                    Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
+
+                    // Set the favoriteIconBitmap.
+                    ImageView bookmarkFavoriteIcon = (ImageView) view;
+                    bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
+                    return true;
+                } else {  // Process the rest of the bookmarksAdapter with default settings.
+                    return false;
+                }
+            }
+        });
+
+        // Update the ListView.
+        bookmarksListView.setAdapter(bookmarksAdapter);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java b/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java
new file mode 100644 (file)
index 0000000..45344d3
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class BookmarksDatabaseHandler extends SQLiteOpenHelper {
+    private static final int SCHEMA_VERSION = 1;
+    private static final String BOOKMARKS_DATABASE = "bookmarks.db";
+    private static final String BOOKMARKS_TABLE = "bookmarks";
+
+    public static final String ID = "_id";
+    public static final String DISPLAYORDER = "displayorder";
+    public static final String BOOKMARK_NAME = "bookmarkname";
+    public static final String BOOKMARK_URL = "bookmarkurl";
+    public static final String PARENTFOLDER = "parentfolder";
+    public static final String ISFOLDER = "isfolder";
+    public static final String FAVORITEICON = "favoriteicon";
+
+    public BookmarksDatabaseHandler(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
+        super(context, BOOKMARKS_DATABASE, factory, SCHEMA_VERSION);
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase bookmarksDatabase) {
+        // Create the database if it doesn't exist.
+        String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" +
+                ID + " integer primary key, " +
+                DISPLAYORDER + " integer, " +
+                BOOKMARK_NAME + " text, " +
+                BOOKMARK_URL + " text, " +
+                PARENTFOLDER + " text, " +
+                ISFOLDER + " boolean, " +
+                FAVORITEICON + " blob);";
+
+        bookmarksDatabase.execSQL(CREATE_BOOKMARKS_TABLE);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase bookmarksDatabase, int oldVersion, int newVersion) {
+        // Code for upgrading the database will be added here when the schema version > 1.
+    }
+
+    public void createBookmark(String bookmarkName, String bookmarkURL, byte[] favoriteIcon) {
+        ContentValues bookmarkContentValues = new ContentValues();
+
+        // ID is created automatically.
+        bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName);
+        bookmarkContentValues.put(BOOKMARK_URL, bookmarkURL);
+        bookmarkContentValues.put(PARENTFOLDER, "");
+        bookmarkContentValues.put(ISFOLDER, false);
+        bookmarkContentValues.put(FAVORITEICON, favoriteIcon);
+
+        // Get a writable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getWritableDatabase();
+
+        // The second argument is "null", which makes it so that completely null rows cannot be created.  Not a problem in our case.
+        bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues);
+
+        // Close the database handle.
+        bookmarksDatabase.close();
+    }
+
+    public Cursor getBookmarksCursor() {
+        // Get a readable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
+
+        // Get everything in the BOOKMARKS_TABLE.
+        String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE;
+
+        // Return the results as a Cursor.  The second argument is "null" because there are no selectionArgs.
+        // We can't close the Cursor because we need to use it in the parent activity.
+        return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null);
+    }
+
+    public String getBookmarkURL(int databaseID) {
+        // Get a readable database handle.
+        SQLiteDatabase bookmarksDatabase = this.getReadableDatabase();
+
+        // Get the row for the selected databaseID.
+        String GET_BOOKMARK_URL = "Select * FROM " + BOOKMARKS_TABLE +
+                " WHERE " + ID + " = " + databaseID;
+
+        // Save the results as Cursor and move it to the first (only) row.  The second argument is "null" because there are no selectionArgs.
+        Cursor bookmarksCursor = bookmarksDatabase.rawQuery(GET_BOOKMARK_URL, null);
+        bookmarksCursor.moveToFirst();
+
+        // Get the int that identifies the "BOOKMARK_URL" column and save the string as bookmarkURL.
+        int urlColumnInt = bookmarksCursor.getColumnIndex(BOOKMARK_URL);
+        String bookmarkURLString = bookmarksCursor.getString(urlColumnInt);
+
+        // Close the Cursor and the database handle.
+        bookmarksCursor.close();
+        bookmarksDatabase.close();
+
+        // Return the bookmarkURLString.
+        return bookmarkURLString;
+    }
+}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/CreateBookmark.java b/app/src/main/java/com/stoutner/privacybrowser/CreateBookmark.java
new file mode 100644 (file)
index 0000000..f844ca3
--- /dev/null
@@ -0,0 +1,150 @@
+/**
+ * Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+ *
+ * Privacy Browser is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.stoutner.privacybrowser;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+// If we don't use android.support.v7.app.AlertDialog instead of android.app.AlertDialog then the dialog will be covered by the keyboard.
+import android.support.v7.app.AlertDialog;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+
+public class CreateBookmark extends DialogFragment {
+    // The public interface is used to send information back to the parent activity.
+    public interface CreateBookmarkListener {
+        void onCreateBookmarkCancel(DialogFragment createBookmarkDialogFragment);
+
+        void onCreateBookmarkCreate(DialogFragment createBookmarkDialogFragment);
+    }
+
+    // createBookmarkListener is used in onAttach() and onCreateDialog()
+    private CreateBookmarkListener createBookmarkListener;
+
+    // Check to make sure the parent activity implements the listener.
+    public void onAttach(Activity parentActivity) {
+        super.onAttach(parentActivity);
+        try {
+            createBookmarkListener = (CreateBookmarkListener) parentActivity;
+        } catch(ClassCastException exception) {
+            throw new ClassCastException(parentActivity.toString() + " must implement CreateBookmarkListener.");
+        }
+    }
+
+    // onCreateDialog requires @NonNull.
+    @Override
+    @NonNull
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Create a drawable version of the favorite icon.
+        Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIcon);
+
+        // Get the activity's layout inflater.
+        LayoutInflater customDialogInflater = getActivity().getLayoutInflater();
+
+        // Use AlertDialog.Builder to create the AlertDialog.  The style formats the color of the button text.
+        AlertDialog.Builder createBookmarkDialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowser_AlertDialog);
+        createBookmarkDialogBuilder.setTitle(R.string.create_bookmark);
+        createBookmarkDialogBuilder.setIcon(favoriteIconDrawable);
+        // The parent view is "null" because it will be assigned by AlertDialog.
+        createBookmarkDialogBuilder.setView(customDialogInflater.inflate(R.layout.create_bookmark_dialog, null));
+
+        // Set an onClick listener on the negative button.
+        createBookmarkDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                createBookmarkListener.onCreateBookmarkCancel(CreateBookmark.this);
+            }
+        });
+
+        // Set an onClick listener on the positive button.
+        createBookmarkDialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                createBookmarkListener.onCreateBookmarkCreate(CreateBookmark.this);
+            }
+        });
+
+
+        // Create an AlertDialog from the AlertDialog.Builder.
+        final AlertDialog createBookmarkDialog = createBookmarkDialogBuilder.create();
+
+        // Show the keyboard when the Dialog is displayed on the screen.
+        createBookmarkDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+        // We need to show the AlertDialog before we can call setOnKeyListener() below.
+        createBookmarkDialog.show();
+
+        // Allow the "enter" key on the keyboard to create the bookmark from "create_bookmark_name_edittext".
+        EditText createBookmarkNameEditText = (EditText) createBookmarkDialog.findViewById(R.id.create_bookmark_name_edittext);
+        assert createBookmarkNameEditText != null;  // Remove the warning below that createBookmarkNameEditText might be null.
+        createBookmarkNameEditText.setOnKeyListener(new View.OnKeyListener() {
+            public boolean onKey(View v, int keyCode, KeyEvent event) {
+                // If the event is a key-down on the "enter" button, select the PositiveButton "Create".
+                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+                    // Trigger the createBookmarkListener.
+                    createBookmarkListener.onCreateBookmarkCreate(CreateBookmark.this);
+
+                    // Manually dismiss the AlertDialog.
+                    createBookmarkDialog.dismiss();
+
+                    // Consume the event.
+                    return true;
+                } else {  // If any other key was pressed, do not consume the event.
+                    return false;
+                }
+            }
+        });
+
+        // Set the formattedUrlString as the initial text of "create_bookmark_url_edittext".
+        EditText createBookmarkUrlEditText = (EditText) createBookmarkDialog.findViewById(R.id.create_bookmark_url_edittext);
+        assert createBookmarkUrlEditText != null;// Remove the warning below that createBookmarkUrlEditText might be null.
+        createBookmarkUrlEditText.setText(MainWebViewActivity.formattedUrlString);
+
+        // Allow the "enter" key on the keyboard to create the bookmark from "create_bookmark_url_edittext".
+        createBookmarkUrlEditText.setOnKeyListener(new View.OnKeyListener() {
+            public boolean onKey(View v, int keyCode, KeyEvent event) {
+                // If the event is a key-down on the "enter" button, select the PositiveButton "Create".
+                if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+                    // Trigger the create listener.
+                    createBookmarkListener.onCreateBookmarkCreate(CreateBookmark.this);
+
+                    // Manually dismiss the AlertDialog.
+                    createBookmarkDialog.dismiss();
+
+                    // Consume the event.
+                    return true;
+                } else { // If any other key was pressed, do not consume the event.
+                    return false;
+                }
+            }
+        });
+
+        // onCreateDialog requires the return of an AlertDialog
+        return createBookmarkDialog;
+    }
+}
\ No newline at end of file
index 9808d407a9e849f4c9e3003b5e2f234a787fa1e2..f4f4034eac9c8ce69d09008550c7078f470e3281 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright 2015 Soren Stoutner <soren@stoutner.com>.
+ * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
  *
@@ -21,37 +21,38 @@ package com.stoutner.privacybrowser;
 
 import android.app.Activity;
 import android.app.Dialog;
+import android.app.DialogFragment;
 import android.content.DialogInterface;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
-import android.support.v4.app.DialogFragment;
+// If we don't use android.support.v7.app.AlertDialog instead of android.app.AlertDialog then the dialog will be covered by the keyboard.
 import android.support.v7.app.AlertDialog;
-import android.support.v7.app.AppCompatDialogFragment;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 import android.widget.EditText;
 
-public class CreateHomeScreenShortcut extends AppCompatDialogFragment {
-    // The public interface is used to send information back to the activity that called CreateHomeScreenShortcut.
+public class CreateHomeScreenShortcut extends DialogFragment {
+    // The public interface is used to send information back to the parent activity.
     public interface CreateHomeScreenSchortcutListener {
-        void onCreateHomeScreenShortcutCancel(DialogFragment dialog);
+        void onCreateHomeScreenShortcutCancel(DialogFragment dialogFragment);
 
-        void onCreateHomeScreenShortcutCreate(DialogFragment dialog);
+        void onCreateHomeScreenShortcutCreate(DialogFragment dialogFragment);
     }
 
-    private CreateHomeScreenSchortcutListener buttonListener;
+    //createHomeScreenShortcutListener is used in onAttach and and onCreateDialog.
+    private CreateHomeScreenSchortcutListener createHomeScreenShortcutListener;
 
-    // Check to make sure that the activity that called CreateHomeScreenShortcut implements both listeners.
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
+    // Check to make sure that the parent activity implements the listener.
+    public void onAttach(Activity parentActivity) {
+        super.onAttach(parentActivity);
         try {
-            buttonListener = (CreateHomeScreenSchortcutListener) activity;
-        } catch (ClassCastException e) {
-            throw new ClassCastException(activity.toString() + " must implement CreateHomeScreenShortcutListener.");
+            createHomeScreenShortcutListener = (CreateHomeScreenSchortcutListener) parentActivity;
+        } catch(ClassCastException exception) {
+            throw new ClassCastException(parentActivity.toString() + " must implement CreateHomeScreenShortcutListener.");
         }
     }
 
@@ -62,57 +63,64 @@ public class CreateHomeScreenShortcut extends AppCompatDialogFragment {
         // Create a drawable version of the favorite icon.
         Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIcon);
 
-        // Use AlertDialog.Builder to create the AlertDialog
-        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
+        // Get the activity's layout inflater.
         LayoutInflater customDialogInflater = getActivity().getLayoutInflater();
-        alertDialogBuilder.setTitle(R.string.shortcut_name);
-        alertDialogBuilder.setIcon(favoriteIconDrawable);
-        alertDialogBuilder.setView(customDialogInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null));
-        alertDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+
+        // Use AlertDialog.Builder to create the AlertDialog.  The style formats the color of the button text.
+        AlertDialog.Builder createHomeScreenShorcutDialogBuilder = new AlertDialog.Builder(getActivity(), R.style.PrivacyBrowser_AlertDialog);
+        createHomeScreenShorcutDialogBuilder.setTitle(R.string.create_shortcut);
+        createHomeScreenShorcutDialogBuilder.setIcon(favoriteIconDrawable);
+        // The parent view is "null" because it will be assigned by AlertDialog.
+        createHomeScreenShorcutDialogBuilder.setView(customDialogInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null));
+
+        // Set an onClick listener on the negative button.
+        createHomeScreenShorcutDialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
-                buttonListener.onCreateHomeScreenShortcutCancel(CreateHomeScreenShortcut.this);
+                createHomeScreenShortcutListener.onCreateHomeScreenShortcutCancel(CreateHomeScreenShortcut.this);
             }
         });
-        alertDialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
+
+        // Set an onClick listener on the positive button.
+        createHomeScreenShorcutDialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int which) {
-                buttonListener.onCreateHomeScreenShortcutCreate(CreateHomeScreenShortcut.this);
+                createHomeScreenShortcutListener.onCreateHomeScreenShortcutCreate(CreateHomeScreenShortcut.this);
             }
         });
 
-        // Assign the alertDialogBuilder to an AlertDialog.
-        final AlertDialog alertDialog = alertDialogBuilder.create();
 
-        // Show the keyboard when the dialog is displayed on the screen.
-        alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        // Create an AlertDialog from the AlertDialogBuilder.
+        final AlertDialog createHomeScreenShortcutAlertDialog = createHomeScreenShorcutDialogBuilder.create();
 
-        // We need to show alertDialog before we can setOnKeyListener below.
-        alertDialog.show();
+        // Show the keyboard when the Dialog is displayed on the screen.
+        createHomeScreenShortcutAlertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
 
-        EditText shortcutNameEditText = (EditText) alertDialog.findViewById(R.id.shortcutNameEditText);
+        // We need to show the AlertDialog before we can call setOnKeyListener() below.
+        createHomeScreenShortcutAlertDialog.show();
 
         // Allow the "enter" key on the keyboard to create the shortcut.
+        EditText shortcutNameEditText = (EditText) createHomeScreenShortcutAlertDialog.findViewById(R.id.shortcut_name_edittext);
+        assert shortcutNameEditText != null;  // Remove the warning below that shortcutNameEditText might be null.
         shortcutNameEditText.setOnKeyListener(new View.OnKeyListener() {
             public boolean onKey(View v, int keyCode, KeyEvent event) {
-                // If the event is a key-down event on the "enter" button, select the PositiveButton "Create".
+                // If the event is a key-down on the "enter" button, select the PositiveButton "Create".
                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
                     // Trigger the create listener.
-                    buttonListener.onCreateHomeScreenShortcutCreate(CreateHomeScreenShortcut.this);
+                    createHomeScreenShortcutListener.onCreateHomeScreenShortcutCreate(CreateHomeScreenShortcut.this);
 
-                    // Manually dismiss alertDialog.
-                    alertDialog.dismiss();
+                    // Manually dismiss the AlertDialog.
+                    createHomeScreenShortcutAlertDialog.dismiss();
 
                     // Consume the event.
                     return true;
-                } else {
-                    // If any other key was pressed, do not consume the event.
+                } else {  // If any other key was pressed, do not consume the event.
                     return false;
                 }
             }
         });
 
         // onCreateDialog requires the return of an AlertDialog.
-        return alertDialog;
+        return createHomeScreenShortcutAlertDialog;
     }
 }
\ No newline at end of file
index e54ac6a511639fda84e1bfe1ba2d9a57c24b9984..df8f812e1ec2e99b59abae0072664ffcd8effa62 100644 (file)
@@ -36,8 +36,8 @@ public class GuideActivity extends AppCompatActivity {
         setContentView(R.layout.guide_coordinatorlayout);
 
         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
-        Toolbar supportAppBar = (Toolbar) findViewById(R.id.guide_toolbar);
-        setSupportActionBar(supportAppBar);
+        Toolbar guideAppBar = (Toolbar) findViewById(R.id.guide_toolbar);
+        setSupportActionBar(guideAppBar);
 
         // Display the home arrow on supportAppBar.
         final ActionBar appBar = getSupportActionBar();
index 66fe512e549bf60eeaa4b89efb761aed46c369dd..c83b8d6b870e3c1f8e543da4d6dbff50a4518915 100644 (file)
@@ -21,6 +21,7 @@ package com.stoutner.privacybrowser;
 
 import android.annotation.SuppressLint;
 import android.app.Activity;
+import android.app.DialogFragment;
 import android.app.DownloadManager;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -32,14 +33,12 @@ import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.support.design.widget.NavigationView;
 import android.support.design.widget.Snackbar;
-import android.support.v4.app.DialogFragment;
 import android.support.v4.view.GravityCompat;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.ActionBarDrawerToggle;
 import android.support.v7.app.AppCompatActivity;
-import android.support.v7.app.AppCompatDialogFragment;
 import android.support.v7.widget.Toolbar;
 import android.util.Patterns;
 import android.view.KeyEvent;
@@ -66,46 +65,70 @@ import java.net.URLEncoder;
 
 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener {
-    // favoriteIcon is public static so it can be accessed from CreateHomeScreenShortcut.
+    // favoriteIcon is public static so it can be accessed from CreateHomeScreenShortcut and BookmarksActivity.
+    // It is also used in onCreate() and onCreateHomeScreenShortcutCreate().
     public static Bitmap favoriteIcon;
-    // mainWebView is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onOptionsItemSelected(), onNavigationItemSelected(), and loadUrlFromTextBox().
+
+    // mainWebView is public static so it can be accessed from SettingsFragment.
+    // It is also used in onCreate(), onOptionsItemSelected(), onNavigationItemSelected(), and loadUrlFromTextBox().
     public static WebView mainWebView;
 
+    // formattedUrlString is public static so it can be accessed from BookmarksActivity.
+    // It is also used in onCreate(), onOptionsItemSelected(), onCreateHomeScreenShortcutCreate(), and loadUrlFromTextBox().
+    public static String formattedUrlString;
+
     // mainMenu is public static so it can be accessed from SettingsFragment.  It is also used in onCreateOptionsMenu() and onOptionsItemSelected().
     public static Menu mainMenu;
+
     // cookieManager is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onOptionsItemSelected(), and onNavigationItemSelected().
     public static CookieManager cookieManager;
-    // javaScriptEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), onOptionsItemSelected(), and loadUrlFromTextBox().
+
+    // javaScriptEnabled is public static so it can be accessed from SettingsFragment.
+    // It is also used in onCreate(), onCreateOptionsMenu(), onOptionsItemSelected(), and loadUrlFromTextBox().
     public static boolean javaScriptEnabled;
-    // firstPartyCookiesEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), onPrepareOptionsMenu(), and onOptionsItemSelected().
+
+    // firstPartyCookiesEnabled is public static so it can be accessed from SettingsFragment.
+    // It is also used in onCreate(), onCreateOptionsMenu(), onPrepareOptionsMenu(), and onOptionsItemSelected().
     public static boolean firstPartyCookiesEnabled;
+
     // thirdPartyCookiesEnabled is used in onCreate(), onCreateOptionsMenu(), onPrepareOptionsMenu(), and onOptionsItemSelected().
     public static boolean thirdPartyCookiesEnabled;
+
     // domStorageEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), and onOptionsItemSelected().
     public static boolean domStorageEnabled;
+
     // saveFormDataEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), and onOptionsItemSelected().
     public static boolean saveFormDataEnabled;
+
     // javaScriptDisabledSearchURL is public static so it can be accessed from SettingsFragment.  It is also used in onCreate() and loadURLFromTextBox().
     public static String javaScriptDisabledSearchURL;
+
     // javaScriptEnabledSearchURL is public static so it can be accessed from SettingsFragment.  It is also used in onCreate() and loadURLFromTextBox().
     public static String javaScriptEnabledSearchURL;
+
     // homepage is public static so it can be accessed from  SettingsFragment.  It is also used in onCreate() and onOptionsItemSelected().
     public static String homepage;
+
     // swipeToRefresh is public static so it can be accessed from SettingsFragment.  It is also used in onCreate().
     public static SwipeRefreshLayout swipeToRefresh;
+
     // swipeToRefreshEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate().
     public static boolean swipeToRefreshEnabled;
 
+
+
     // drawerToggle is used in onCreate(), onPostCreate(), onConfigurationChanged(), onNewIntent(), and onNavigationItemSelected().
     private ActionBarDrawerToggle drawerToggle;
+
     // drawerLayout is used in onCreate(), onNewIntent(), and onBackPressed().
     private DrawerLayout drawerLayout;
-    // formattedUrlString is used in onCreate(), onOptionsItemSelected(), onCreateHomeScreenShortcutCreate(), and loadUrlFromTextBox().
-    private String formattedUrlString;
+
     // privacyIcon is used in onCreateOptionsMenu() and updatePrivacyIcon().
     private MenuItem privacyIcon;
+
     // urlTextBox is used in onCreate(), onOptionsItemSelected(), and loadUrlFromTextBox().
     private EditText urlTextBox;
+
     // adView is used in onCreate() and onConfigurationChanged().
     private View adView;
 
@@ -604,9 +627,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 return true;
 
             case R.id.addToHomescreen:
-                // Show the CreateHomeScreenShortcut AlertDialog and name this instance createShortcut.
-                AppCompatDialogFragment shortcutDialog = new CreateHomeScreenShortcut();
-                shortcutDialog.show(getSupportFragmentManager(), "createShortcut");
+                // Show the CreateHomeScreenShortcut AlertDialog and name this instance "@string/create_shortcut".
+                DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
+                createHomeScreenShortcutDialogFragment.show(getFragmentManager(), "@string/create_shortcut");
 
                 //Everything else will be handled by CreateHomeScreenShortcut and the associated listeners below.
                 return true;
@@ -644,6 +667,12 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 }
                 break;
 
+            case R.id.bookmarks:
+                // Launch BookmarksActivity.
+                Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
+                startActivity(bookmarksIntent);
+                break;
+
             case R.id.downloads:
                 // Launch the system Download Manager.
                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
@@ -654,18 +683,18 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 startActivity(downloadManagerIntent);
                 break;
 
-            case R.id.guide:
-                // Launch GuideActivity.
-                Intent guideIntent = new Intent(this, GuideActivity.class);
-                startActivity(guideIntent);
-                break;
-
             case R.id.settings:
                 // Launch SettingsActivity.
                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
                 startActivity(settingsIntent);
                 break;
 
+            case R.id.guide:
+                // Launch GuideActivity.
+                Intent guideIntent = new Intent(this, GuideActivity.class);
+                startActivity(guideIntent);
+                break;
+
             case R.id.about:
                 // Launch AboutActivity.
                 Intent aboutIntent = new Intent(this, AboutActivity.class);
@@ -684,6 +713,10 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
                 WebStorage domStorage = WebStorage.getInstance();
                 domStorage.deleteAllData();
 
+                // Clear form data.
+                WebViewDatabase formData = WebViewDatabase.getInstance(this);
+                formData.clearFormData();
+
                 // Clear cache.  The argument of "true" includes disk files.
                 mainWebView.clearCache(true);
 
@@ -730,14 +763,14 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
     }
 
     @Override
-    public void onCreateHomeScreenShortcutCancel(DialogFragment dialog) {
+    public void onCreateHomeScreenShortcutCancel(DialogFragment dialogFragment) {
         // Do nothing because the user selected "Cancel".
     }
 
     @Override
-    public void onCreateHomeScreenShortcutCreate(DialogFragment dialog) {
+    public void onCreateHomeScreenShortcutCreate(DialogFragment dialogFragment) {
         // Get shortcutNameEditText from the alert dialog.
-        EditText shortcutNameEditText = (EditText) dialog.getDialog().findViewById(R.id.shortcutNameEditText);
+        EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
 
         // Create the bookmark shortcut based on formattedUrlString.
         Intent bookmarkShortcut = new Intent();
index 2afa9e789437830ef7dacf4f2c5c4c5856af33b6..cd6a81e1e4dfae4ce34720711df7e350f0b783f4 100644 (file)
@@ -235,7 +235,7 @@ public class SettingsFragment extends PreferenceFragment {
                         customUserAgent.setSummary(sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
 
                         // Update mainWebView's user agent.  The default is "PrivacyBrowser/1.0".
-                        MainWebViewActivity.mainWebView.getSettings().setUserAgentString(sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"));
+                        MainWebViewActivity.mainWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
                         break;
 
                     case "javascript_disabled_search":
diff --git a/app/src/main/res/drawable/add.xml b/app/src/main/res/drawable/add.xml
new file mode 100644 (file)
index 0000000..92e17d1
--- /dev/null
@@ -0,0 +1,14 @@
+<!-- add.xml comes from the Android Material icon set, where it is called ic_add.
+  It is released under the CC-BY license <https://creativecommons.org/licenses/by/4.0/>. -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0" >
+
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/bookmarks.xml b/app/src/main/res/drawable/bookmarks.xml
new file mode 100644 (file)
index 0000000..7676249
--- /dev/null
@@ -0,0 +1,14 @@
+<!-- bookmarks.xml comes from the Android Material icon set, where it is called ic_bookmark_border.
+  It is released under the CC-BY license <https://creativecommons.org/licenses/by/4.0/>. -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportHeight="24.0"
+    android:viewportWidth="24.0" >
+
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M17,3L7,3c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,5h10v13z"/>
+</vector>
index 4ec9d4d1a83e2c79c2442d4166b2c7eebf98278b..46fe249750c8b2976bfbdfad018a8b782f760c9d 100644 (file)
@@ -31,8 +31,8 @@
 
     <!-- the LinearLayout with orientation="vertical" moves the ViewPager below the AppBarLayout. -->
     <LinearLayout
-        android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:layout_width="match_parent"
         android:orientation="vertical" >
 
         <!-- We need to set android:background="@color/blue" here or any space to the right of the TabLayout on large devices will be white. -->
@@ -46,8 +46,8 @@
             <!-- android:theme="@style/PrivacyBrowser.DarkAppBar" makes the text and icons in the AppBar white. -->
             <android.support.v7.widget.Toolbar
                 android:id="@+id/about_toolbar"
-                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
+                android:layout_width="match_parent"
                 android:background="@color/blue"
                 android:theme="@style/PrivacyBrowser.DarkAppBar"
                 app:popupTheme="@style/PrivacyBrowser.PopupOverlay" />
diff --git a/app/src/main/res/layout/bookmarks_coordinatorlayout.xml b/app/src/main/res/layout/bookmarks_coordinatorlayout.xml
new file mode 100644 (file)
index 0000000..152e3b3
--- /dev/null
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<!-- android:fitsSystemWindows="true" moves the AppBar below the status bar.
+  When it is specified the theme should include <item name="android:windowTranslucentStatus">true</item>
+  to make the status bar a transparent, darkened overlay. -->
+<android.support.design.widget.CoordinatorLayout
+    android:id="@+id/bookmarks_coordinatorlayout"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:fitsSystemWindows="true" >
+
+    <!-- the LinearLayout with orientation="vertical" moves the content below the AppBarLayout. -->
+    <LinearLayout
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical" >
+
+        <android.support.design.widget.AppBarLayout
+            android:id="@+id/bookmarks_appbarlayout"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:theme="@style/PrivacyBrowser.AppBarOverlay" >
+
+            <!-- android:theme="@style/PrivacyBrowser.DarkAppBar" makes the text and icons in the AppBar white. -->
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/bookmarks_toolbar"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent"
+                android:background="@color/blue"
+                android:theme="@style/PrivacyBrowser.DarkAppBar"
+                app:popupTheme="@style/PrivacyBrowser.PopupOverlay" />
+        </android.support.design.widget.AppBarLayout>
+
+        <ListView
+            android:id="@+id/bookmarks_listview"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent" />
+    </LinearLayout>
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/create_bookmark_fab"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        android:src="@drawable/add" />
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/bookmarks_item_linearlayout.xml b/app/src/main/res/layout/bookmarks_item_linearlayout.xml
new file mode 100644 (file)
index 0000000..c1a8f21
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+    android:id="@+id/bookmarks_item_linearlayout"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="horizontal"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <ImageView
+        android:id="@+id/bookmark_favorite_icon"
+        android:layout_height="30dp"
+        android:layout_width="30dp"
+        android:layout_gravity="center_vertical"
+        android:layout_marginStart="10dp"
+        tools:ignore="ContentDescription" />
+
+    <TextView
+        android:id="@+id/bookmark_name"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:textColor="@color/black"
+        android:textSize="22sp"
+        android:layout_margin="10dp"
+        android:singleLine="true" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/create_bookmark_dialog.xml b/app/src/main/res/layout/create_bookmark_dialog.xml
new file mode 100644 (file)
index 0000000..35a5e02
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2016 Soren Stoutner <soren@stoutner.com>.
+
+  This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
+
+  Privacy Browser is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Privacy Browser is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
+
+<LinearLayout
+    android:id="@+id/create_bookmark_dialog_linearlayout"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent" >
+
+    <!-- android:imeOptions="actionGo" sets the keyboard to have a "go" key instead of a "new line" key.
+        android:imeOptions=flagNoExtractUi" doesn't cover up the entire UI when typing in landscape orientation.
+        android:inputType="textUri" disables spell check in the EditText.
+        android:singleLine="true" is not needed in a Dialog.-->
+    <EditText
+        android:id="@+id/create_bookmark_name_edittext"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_marginTop="16dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="4dp"
+        android:hint="@string/bookmark_name"
+        android:imeOptions="actionGo|flagNoExtractUi"
+        android:inputType="textUri" />
+
+    <EditText
+        android:id="@+id/create_bookmark_url_edittext"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:layout_marginTop="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_marginRight="4dp"
+        android:layout_marginBottom="16dp"
+        android:hint="@string/bookmark_url"
+        android:imeOptions="actionGo|flagNoExtractUi"
+        android:inputType="textUri" />
+
+</LinearLayout>
\ No newline at end of file
index 6cf50f006c2f2ce3d20bfc4d948579a3af0c54df..32d2eb4d997fce68b98497025c4246695494e5c9 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!--
-  Copyright 2015 Soren Stoutner <soren@stoutner.com>.
+  Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
 
   This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
 
   along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>. -->
 
 <RelativeLayout
-    android:id="@+id/createHomeScreenShortcutDialogRelativeLayout"
+    android:id="@+id/create_home_screen_shortcut_dialog_relativelayout"
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" >
 
-    <!-- android:imeOptions="actionGo" sets the keyboard to have a "go" key instead of a "new line" key. -->
-    <!-- android:imeOptions=flagNoExtractUi" doesn't cover up the entire UI when typing in landscape orientation. -->
-    <!-- android:inputType="textUri" disables spell check in the EditText. -->
+    <!-- android:imeOptions="actionGo" sets the keyboard to have a "go" key instead of a "new line" key.
+        android:imeOptions=flagNoExtractUi" doesn't cover up the entire UI when typing in landscape orientation.
+        android:inputType="textUri" disables spell check in the EditText.
+        android:singleLine="true" is not needed in a Dialog.-->
     <EditText
-        android:id="@+id/shortcutNameEditText"
+        android:id="@+id/shortcut_name_edittext"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="16dp"
         android:layout_marginLeft="4dp"
         android:layout_marginRight="4dp"
         android:layout_marginBottom="16dp"
+        android:hint="@string/shortcut_name"
         android:imeOptions="actionGo|flagNoExtractUi"
         android:inputType="textUri" />
 
index 6b2761a83e9ac3dffd403d55cee8b4f1d3bfacc0..756013c3ae2200cdc54382da99202bd6ee7421f8 100644 (file)
     <group
         android:id="@+id/navigationGroup1" >
         <item
-            android:id="@+id/downloads"
-            android:title="@string/downloads"
-            android:icon="@drawable/downloads"
+            android:id="@+id/bookmarks"
+            android:title="@string/bookmarks"
+            android:icon="@drawable/bookmarks"
             android:orderInCategory="40" />
 
         <item
-            android:id="@+id/guide"
-            android:title="@string/guide"
-            android:icon="@drawable/guide"
+            android:id="@+id/downloads"
+            android:title="@string/downloads"
+            android:icon="@drawable/downloads"
             android:orderInCategory="50" />
+    </group>
 
+    <group
+        android:id="@+id/navigationGroup2" >
         <item
             android:id="@+id/settings"
             android:title="@string/settings"
             android:icon="@drawable/settings"
             android:orderInCategory="60" />
 
+        <item
+            android:id="@+id/guide"
+            android:title="@string/guide"
+            android:icon="@drawable/guide"
+            android:orderInCategory="70" />
+
         <item
             android:id="@+id/about"
             android:title="@string/about"
             android:icon="@drawable/about"
-            android:orderInCategory="70" />
+            android:orderInCategory="80" />
     </group>
 
     <!-- If a group has an id, a line is drawn above it in the navigation view. -->
     <group
-        android:id="@+id/navigationGroup2" >
+        android:id="@+id/navigationGroup3" >
         <item
             android:id="@+id/clearAndExit"
             android:title="@string/clear_and_exit"
             android:icon="@drawable/exit"
-            android:orderInCategory="80" />
+            android:orderInCategory="90" />
     </group>
 </menu>
\ No newline at end of file
index d039bcbd1cfafc995d43b7d73d1dd700a3cdecf4..ea2d867a76074a39fc0fe3f4184e8d95f13c4bb1 100644 (file)
@@ -28,7 +28,7 @@
     <!-- android:windowTranslucentStatus requires API >= 19.  It makes the system status bar transparent.
       When it is specified the root layout should include android:fitsSystemWindows="true".
       colorPrimaryDark goes behind the status bar, which is then darkened by the overlay. -->
-    <style name="PrivacyBrowser.TabActivity">
+    <style name="PrivacyBrowser.SecondaryActivity">
         <item name="android:windowTranslucentStatus">true</item>
         <item name="colorPrimaryDark">@color/blue</item>
     </style>
index eabe4beb9961ad7151d3246f17e1989b896708bc..77428aeeb866c53adca87a51b1700d30bde25c40 100644 (file)
     <string name="home">Home</string>
     <string name="back">Back</string>
     <string name="forward">Forward</string>
+    <string name="bookmarks">Bookmarks</string>
     <string name="downloads">Downloads</string>
-    <string name="guide">Guide</string>
     <string name="settings">Settings</string>
+    <string name="guide">Guide</string>
     <string name="about">About</string>
     <string name="clear_and_exit">Clear and Exit</string>
 
     <string name="refresh">Refresh</string>
 
     <!-- Create Home Screen Shortcut Alert Dialog. -->
+    <string name="create_shortcut">Create shortcut</string>
     <string name="shortcut_name">Shortcut name</string>
     <string name="cancel">Cancel</string>
     <string name="create">Create</string>
 
+    <!-- Bookmarks. -->
+    <string name="create_bookmark">Create bookmark</string>
+    <string name="bookmark_name">Bookmark name</string>
+    <string name="bookmark_url">Bookmark URL</string>
+
     <!-- Guide. -->
     <string name="privacy_browser_guide">Privacy Browser Guide</string>
     <string name="overview">Overview</string>
index 371d01348dba53b32cb71837b1cfdeabf535d47c..36b667850ef9582e7ea0d048fcd1c6ee9de41b59 100644 (file)
@@ -26,7 +26,7 @@
 
     <style name="PrivacyBrowser.MainWebView" />
 
-    <style name="PrivacyBrowser.TabActivity" />
+    <style name="PrivacyBrowser.SecondaryActivity" />
 
     <!-- colorPrimaryDark is the color of the status bar. -->
     <style name="PrivacyBrowser.Settings" parent="Theme.AppCompat.Light.DarkActionBar">
@@ -42,4 +42,8 @@
 
     <style name="PrivacyBrowser.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
 
+    <style name="PrivacyBrowser.AlertDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
+        <item name="colorAccent">@color/blue</item>
+    </style>
+
 </resources>
\ No newline at end of file