From: Soren Stoutner Date: Fri, 10 Mar 2017 22:52:03 +0000 (-0700) Subject: Implement a working two-paned mode for `DomainsActivity`. X-Git-Tag: v2.0~7 X-Git-Url: https://gitweb.stoutner.com/?a=commitdiff_plain;h=61a76e491469916f2f30aebb47b98cda7cceb557;p=PrivacyBrowserAndroid.git Implement a working two-paned mode for `DomainsActivity`. --- diff --git a/app/src/free/res/layout/main_webview.xml b/app/src/free/res/layout/main_webview.xml index 5aa146ae..6013ae29 100644 --- a/app/src/free/res/layout/main_webview.xml +++ b/app/src/free/res/layout/main_webview.xml @@ -28,7 +28,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" - tools:context="com.stoutner.privacybrowser.activities.MainWebView" + tools:context="com.stoutner.privacybrowser.activities.MainWebViewActivity" tools:showIn="@layout/drawerlayout"> @@ -131,10 +131,23 @@ `android:persistableMode="persistNever"` removes Privacy Browser from the recents screen on a device reboot. `tools:ignore="unusedAttribute"` removes the lint warning that `persistableMode` does not apply to API < 21. --> + + + . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; - -import com.stoutner.privacybrowser.fragments.AboutTab; -import com.stoutner.privacybrowser.R; - -public class About extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.about_coordinatorlayout); - - // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21. - Toolbar aboutAppBar = (Toolbar) findViewById(R.id.about_toolbar); - setSupportActionBar(aboutAppBar); - - // 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); - - // Setup the ViewPager. - ViewPager aboutViewPager = (ViewPager) findViewById(R.id.about_viewpager); - aboutViewPager.setAdapter(new aboutPagerAdapter(getSupportFragmentManager())); - - // Setup the TabLayout and connect it to the ViewPager. - TabLayout aboutTabLayout = (TabLayout) findViewById(R.id.about_tablayout); - aboutTabLayout.setupWithViewPager(aboutViewPager); - } - - public class aboutPagerAdapter extends FragmentPagerAdapter { - private aboutPagerAdapter(FragmentManager fm) { - super(fm); - } - - @Override - // Get the count of the number of tabs. - public int getCount() { - return 7; - } - - @Override - // Get the name of each tab. Tab numbers start at 0. - public CharSequence getPageTitle(int tab) { - switch (tab) { - case 0: - return getString(R.string.version); - - case 1: - return getString(R.string.permissions); - - case 2: - return getString(R.string.privacy_policy); - - case 3: - return getString(R.string.changelog); - - case 4: - return getString(R.string.licenses); - - case 5: - return getString(R.string.contributors); - - case 6: - return getString(R.string.links); - - default: - return ""; - } - } - - @Override - // Setup each tab. - public Fragment getItem(int tab) { - return AboutTab.createTab(tab); - } - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.java new file mode 100644 index 00000000..bc5c7fb6 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/AboutActivity.java @@ -0,0 +1,106 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import com.stoutner.privacybrowser.fragments.AboutTabFragment; +import com.stoutner.privacybrowser.R; + +public class AboutActivity extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.about_coordinatorlayout); + + // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21. + Toolbar aboutAppBar = (Toolbar) findViewById(R.id.about_toolbar); + setSupportActionBar(aboutAppBar); + + // 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); + + // Setup the ViewPager. + ViewPager aboutViewPager = (ViewPager) findViewById(R.id.about_viewpager); + aboutViewPager.setAdapter(new aboutPagerAdapter(getSupportFragmentManager())); + + // Setup the TabLayout and connect it to the ViewPager. + TabLayout aboutTabLayout = (TabLayout) findViewById(R.id.about_tablayout); + aboutTabLayout.setupWithViewPager(aboutViewPager); + } + + private class aboutPagerAdapter extends FragmentPagerAdapter { + private aboutPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + // Get the count of the number of tabs. + public int getCount() { + return 7; + } + + @Override + // Get the name of each tab. Tab numbers start at 0. + public CharSequence getPageTitle(int tab) { + switch (tab) { + case 0: + return getString(R.string.version); + + case 1: + return getString(R.string.permissions); + + case 2: + return getString(R.string.privacy_policy); + + case 3: + return getString(R.string.changelog); + + case 4: + return getString(R.string.licenses); + + case 5: + return getString(R.string.contributors); + + case 6: + return getString(R.string.links); + + default: + return ""; + } + } + + @Override + // Setup each tab. + public Fragment getItem(int tab) { + return AboutTabFragment.createTab(tab); + } + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/Bookmarks.java b/app/src/main/java/com/stoutner/privacybrowser/activities/Bookmarks.java deleted file mode 100644 index 03ee0d17..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/Bookmarks.java +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.design.widget.FloatingActionButton; -import android.support.design.widget.Snackbar; -import android.support.v4.app.NavUtils; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.app.AppCompatDialogFragment; -import android.support.v7.widget.Toolbar; -import android.util.SparseBooleanArray; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.CursorAdapter; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.TextView; - -import com.stoutner.privacybrowser.dialogs.EditBookmark; -import com.stoutner.privacybrowser.dialogs.EditBookmarkFolder; -import com.stoutner.privacybrowser.dialogs.MoveToFolder; -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; -import com.stoutner.privacybrowser.dialogs.CreateBookmark; -import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolder; - -import java.io.ByteArrayOutputStream; - -public class Bookmarks extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener, CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener, EditBookmarkFolder.EditBookmarkFolderListener, - MoveToFolder.MoveToFolderListener { - - // `bookmarksDatabaseHelper` is public static so it can be accessed from `EditBookmark` and `MoveToFolder`. It is also used in `onCreate()`, - // `onCreateBookmarkCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`. - public static BookmarksDatabaseHelper bookmarksDatabaseHelper; - - // `currentFolder` is public static so it can be accessed from `MoveToFolder`. - // It is used in `onCreate`, `onOptionsItemSelected()`, `onCreateBookmarkCreate`, `onCreateBookmarkFolderCreate`, and `onEditBookmarkSave`. - public static String currentFolder; - - // `checkedItemIds` is public static so it can be accessed from `EditBookmark`, `EditBookmarkFolder`, and `MoveToFolder`. - // It is also used in `onActionItemClicked`. - public static long[] checkedItemIds; - - - // `bookmarksListView` is used in `onCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`. - private ListView bookmarksListView; - - // `contextualActionMode` is used in `onCreate()` and `onEditBookmarkSave()`. - private ActionMode contextualActionMode; - - // `selectedBookmarkPosition` is used in `onCreate()` and `onEditBookmarkSave()`. - private int selectedBookmarkPosition; - - // `appBar` is used in `onCreate()` and `updateBookmarksListView()`. - private ActionBar appBar; - - // `bookmarksCursor` is used in `onCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`. - private Cursor bookmarksCursor; - - // `oldFolderName` is used in `onCreate()` and `onEditBookmarkFolderSave()`. - private String oldFolderNameString; - - @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. - final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.bookmarks_toolbar); - setSupportActionBar(bookmarksAppBar); - - // Display the home arrow on `SupportActionBar`. - 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`. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. - // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`. - bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0); - bookmarksListView = (ListView) findViewById(R.id.bookmarks_listview); - - // Set currentFolder to the home folder, which is `""` in the database. - currentFolder = ""; - - // Display the bookmarks in the ListView. - updateBookmarksListView(currentFolder); - - // 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 `Cursor` for this ID and move it to the first row. - Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID); - bookmarkCursor.moveToFirst(); - - // If the bookmark is a folder load its contents into the ListView. - if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { - // Update `currentFolder`. - currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - - // Reload the ListView with `currentFolder`. - updateBookmarksListView(currentFolder); - } else { // Load the URL into `mainWebView`. - // Get the bookmark URL and assign it to formattedUrlString. `mainWebView` will automatically reload when `Bookmarks` closes. - MainWebView.formattedUrlString = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)); - - NavUtils.navigateUpFromSameTask(bookmarksActivity); - } - - // Close the `Cursor`. - bookmarkCursor.close(); - } - }); - - // `MultiChoiceModeListener` handles long clicks. - bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { - // `moveBookmarkUpMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`. - MenuItem moveBookmarkUpMenuItem; - - // `moveBookmarkDownMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`. - MenuItem moveBookmarkDownMenuItem; - - // `editBookmarkMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`. - MenuItem editBookmarkMenuItem; - - // `selectAllBookmarks` is used in `onCreateActionMode()` and `onItemCheckedStateChanges`. - MenuItem selectAllBookmarksMenuItem; - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - // Inflate the menu for the contextual app bar and set the title. - getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu); - - // Set the title. - if (currentFolder.isEmpty()) { - // Use `R.string.bookmarks` if we are in the home folder. - mode.setTitle(R.string.bookmarks); - } else { // Use the current folder name as the title. - mode.setTitle(currentFolder); - } - - // Get a handle for MenuItems we need to selectively disable. - moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up); - moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down); - editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark); - selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks); - - // Get a handle for `contextualActionMode` so we can close it programatically. - contextualActionMode = mode; - - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { - // Get an array of the selected bookmarks. - long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds(); - - // Calculate the number of selected bookmarks. - int numberOfSelectedBookmarks = selectedBookmarksLongArray.length; - - // Adjust the `mode` and the menu for the number of selected bookmarks. - if (numberOfSelectedBookmarks == 0) { - mode.finish(); - } else if (numberOfSelectedBookmarks == 1) { - // List the number of selected bookmarks in the subtitle. - mode.setSubtitle(getString(R.string.one_selected)); - - // Show the `Move Up`, `Move Down`, and `Edit` options. - moveBookmarkUpMenuItem.setVisible(true); - moveBookmarkDownMenuItem.setVisible(true); - editBookmarkMenuItem.setVisible(true); - - // Get the database IDs for the bookmarks. - int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0]; - int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0); - // bookmarksListView is 0 indexed. - int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1); - - // Disable `moveBookmarkUpMenuItem` if the selected bookmark is at the top of the ListView. - if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { - moveBookmarkUpMenuItem.setEnabled(false); - moveBookmarkUpMenuItem.setIcon(R.drawable.move_bookmark_up_disabled); - } else { // Otherwise enable `moveBookmarkUpMenuItem`. - moveBookmarkUpMenuItem.setEnabled(true); - moveBookmarkUpMenuItem.setIcon(R.drawable.move_bookmark_up_enabled); - } - - // Disable `moveBookmarkDownMenuItem` if the selected bookmark is at the bottom of the ListView. - if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { - moveBookmarkDownMenuItem.setEnabled(false); - moveBookmarkDownMenuItem.setIcon(R.drawable.move_bookmark_down_disabled); - } else { // Otherwise enable `moveBookmarkDownMenuItem`. - moveBookmarkDownMenuItem.setEnabled(true); - moveBookmarkDownMenuItem.setIcon(R.drawable.move_bookmark_down_enabled); - } - } else { // More than one bookmark is selected. - // List the number of selected bookmarks in the subtitle. - mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected)); - - // Hide non-applicable `MenuItems`. - moveBookmarkUpMenuItem.setVisible(false); - moveBookmarkDownMenuItem.setVisible(false); - editBookmarkMenuItem.setVisible(false); - } - - // Do not show `Select All` if all the bookmarks are already checked. - if (bookmarksListView.getCheckedItemIds().length == bookmarksListView.getCount()) { - selectAllBookmarksMenuItem.setVisible(false); - } else { - selectAllBookmarksMenuItem.setVisible(true); - } - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - int menuItemId = item.getItemId(); - - // `numberOfBookmarks` is used in `R.id.move_bookmark_up_enabled`, `R.id.move_bookmark_down_enabled`, and `R.id.context_menu_select_all_bookmarks`. - int numberOfBookmarks; - - // `selectedBookmarkLongArray` is used in `R.id.move_bookmark_up`, `R.id.move_bookmark_down`, and `R.id.edit_bookmark`. - long[]selectedBookmarkLongArray; - // `selectedBookmarkDatabaseId` is used in `R.id.move_bookmark_up`, `R.id.move_bookmark_down`, and `R.id.edit_bookmark`. - int selectedBookmarkDatabaseId; - // `selectedBookmarkNewPosition` is used in `R.id.move_bookmark_up` and `R.id.move_bookmark_down`. - int selectedBookmarkNewPosition; - // `bookmarkPositionSparseBooleanArray` is used in `R.id.edit_bookmark` and `R.id.delete_bookmark`. - SparseBooleanArray bookmarkPositionSparseBooleanArray; - - switch (menuItemId) { - case R.id.move_bookmark_up: - // Get the selected bookmark database ID. - selectedBookmarkLongArray = bookmarksListView.getCheckedItemIds(); - selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; - - // Initialize `selectedBookmarkNewPosition`. - selectedBookmarkNewPosition = 0; - - for (int i = 0; i < bookmarksListView.getCount(); i++) { - int databaseId = (int) bookmarksListView.getItemIdAtPosition(i); - int nextBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i + 1); - - if (databaseId == selectedBookmarkDatabaseId || nextBookmarkDatabaseId == selectedBookmarkDatabaseId) { - if (databaseId == selectedBookmarkDatabaseId) { - // Move the selected bookmark up one and store the new bookmark position. - bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i - 1); - selectedBookmarkNewPosition = i - 1; - } else { // Move the bookmark above the selected bookmark down one. - bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i + 1); - } - } else { - // Reset the rest of the bookmarks' DISPLAY_ORDER to match the position in the ListView. - // This isn't necessary, but it clears out any stray values that might have crept into the database. - bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i); - } - } - - // Refresh the ListView. - updateBookmarksListView(currentFolder); - - // Select the previously selected bookmark in the new location. - bookmarksListView.setItemChecked(selectedBookmarkNewPosition, true); - - bookmarksListView.setSelection(selectedBookmarkNewPosition - 5); - - break; - - case R.id.move_bookmark_down: - // Get the selected bookmark database ID. - selectedBookmarkLongArray = bookmarksListView.getCheckedItemIds(); - selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; - - // Initialize `selectedBookmarkNewPosition`. - selectedBookmarkNewPosition = 0; - - for (int i = 0; i 0)) { - String cannotCreateFolder = getResources().getString(R.string.cannot_create_folder) + " \"" + folderNameString + "\""; - Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), cannotCreateFolder, Snackbar.LENGTH_INDEFINITE).show(); - } else { // Create the folder. - // Get the new folder icon `Bitmap`. - RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton); - Bitmap folderIconBitmap; - if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon `ImageView` from the `Dialog` and convert it to a `Bitmap`. - ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon); - Drawable folderIconDrawable = folderIconImageView.getDrawable(); - BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; - folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Assign `favoriteIcon` from the `WebView`. - folderIconBitmap = MainWebView.favoriteIcon; - } - - // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). - ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); - byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); - - // Move all the bookmarks down one in the display order. - for (int i = 0; i < bookmarksListView.getCount(); i++) { - int databaseId = (int) bookmarksListView.getItemIdAtPosition(i); - bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i + 1); - } - - // Create the folder, placing it at the top of the ListView - bookmarksDatabaseHelper.createFolder(folderNameString, 0, currentFolder, folderIconByteArray); - - // Refresh the ListView. - updateBookmarksListView(currentFolder); - } - } - - @Override - public void onSaveEditBookmark(AppCompatDialogFragment dialogFragment) { - // Get a long array with the the databaseId of the selected bookmark and convert it to an `int`. - long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds(); - int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0]; - - // Get the `EditText`s from the `editBookmarkDialogFragment` and extract the strings. - EditText editBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext); - String bookmarkNameString = editBookmarkNameEditText.getText().toString(); - EditText editBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext); - String bookmarkUrlString = editBookmarkUrlEditText.getText().toString(); - - // Get `edit_bookmark_current_icon_radiobutton`. - RadioButton currentBookmarkIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton); - - if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon. - bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString); - } else { // Update the bookmark using the `WebView` favorite icon. - ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); - MainWebView.favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream); - byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray(); - - // Update the bookmark and the favorite icon. - bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray); - } - - // Close the contextual action mode. - contextualActionMode.finish(); - - // Refresh the `ListView`. `setSelection` scrolls to the position of the bookmark that was edited. - updateBookmarksListView(currentFolder); - bookmarksListView.setSelection(selectedBookmarkPosition); - } - - @Override - public void onSaveEditBookmarkFolder(AppCompatDialogFragment dialogFragment) { - // Get the new folder name. - EditText editFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext); - String newFolderNameString = editFolderNameEditText.getText().toString(); - - // Check to see if the new folder name is unique. - Cursor bookmarkFolderCursor = bookmarksDatabaseHelper.getFolderCursor(newFolderNameString); - int existingFoldersWithNewName = bookmarkFolderCursor.getCount(); - bookmarkFolderCursor.close(); - if ( ((existingFoldersWithNewName == 0) || newFolderNameString.equals(oldFolderNameString)) && !newFolderNameString.isEmpty()) { - // Get a long array with the the database ID of the selected folder and convert it to an `int`. - long[] selectedFolderLongArray = bookmarksListView.getCheckedItemIds(); - int selectedFolderDatabaseId = (int) selectedFolderLongArray[0]; - - // Get the `RadioButtons` from the `Dialog`. - RadioButton currentFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton); - RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton); - - // Check if the favorite icon has changed. - if (currentFolderIconRadioButton.isChecked()) { - // Update the folder name if it has changed without modifying the favorite icon. - if (!newFolderNameString.equals(oldFolderNameString)) { - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString); - - // Refresh the `ListView`. `setSelection` scrolls to the position of the folder that was edited. - updateBookmarksListView(currentFolder); - bookmarksListView.setSelection(selectedBookmarkPosition); - } - } else { // Update the folder icon. - // Get the new folder icon `Bitmap`. - Bitmap folderIconBitmap; - if (defaultFolderIconRadioButton.isChecked()) { - // Get the default folder icon `ImageView` from the `Drawable` and convert it to a `Bitmap`. - ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon); - Drawable folderIconDrawable = folderIconImageView.getDrawable(); - BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; - folderIconBitmap = folderIconBitmapDrawable.getBitmap(); - } else { // Get the web page icon `ImageView` from the `Dialog`. - folderIconBitmap = MainWebView.favoriteIcon; - } - - // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). - ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); - folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); - byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); - - bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray); - - // Refresh the `ListView`. `setSelection` scrolls to the position of the folder that was edited. - updateBookmarksListView(currentFolder); - bookmarksListView.setSelection(selectedBookmarkPosition); - } - } else { // Don't edit the folder because the new name is not unique. - String cannot_rename_folder = getResources().getString(R.string.cannot_save_folder) + " \"" + newFolderNameString + "\""; - Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), cannot_rename_folder, Snackbar.LENGTH_INDEFINITE).show(); - } - - // Close the contextual action mode. - contextualActionMode.finish(); - } - - @Override - public void onMoveToFolder(AppCompatDialogFragment dialogFragment) { - // Get the new folder database id. - ListView folderListView = (ListView) dialogFragment.getDialog().findViewById(R.id.move_to_folder_listview); - long[] newFolderLongArray = folderListView.getCheckedItemIds(); - - if (newFolderLongArray.length == 0) { // No new folder was selected. - Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.cannot_move_bookmarks), Snackbar.LENGTH_INDEFINITE).show(); - } else { // Move the selected bookmarks. - // Get the new folder database ID. - int newFolderDatabaseId = (int) newFolderLongArray[0]; - - // Instantiate `newFolderName`. - String newFolderName; - - if (newFolderDatabaseId == 0) { - // The new folder is the home folder, represented as `""` in the database. - newFolderName = ""; - } else { - // Get the new folder name from the database. - newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId); - } - - // Get a long array with the the database ID of the selected bookmarks. - long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds(); - for (long databaseIdLong : selectedBookmarksLongArray) { - // Get `databaseIdInt` for each selected bookmark. - int databaseIdInt = (int) databaseIdLong; - - // Move the selected bookmark to the new folder. - bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName); - } - - // Refresh the `ListView`. - updateBookmarksListView(currentFolder); - - // Close the contextual app bar. - contextualActionMode.finish(); - } - } - - private void updateBookmarksListView(String folderName) { - // Get a `Cursor` with the current contents of the bookmarks database. - bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName); - - // Setup `bookmarksCursorAdapter` with `this` context. `false` disables `autoRequery`. - CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Get the favorite icon byte array from the `Cursor`. - byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); - - // 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); - - // Display the bitmap in `bookmarkFavoriteIcon`. - ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon); - bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); - - - // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. - String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name); - bookmarkNameTextView.setText(bookmarkNameString); - - // Make the font bold for folders. - if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { - bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD); - } else { // Reset the font to default for normal bookmarks. - bookmarkNameTextView.setTypeface(Typeface.DEFAULT); - } - } - }; - - // Update the ListView. - bookmarksListView.setAdapter(bookmarksCursorAdapter); - - // Set the AppBar title. - if (currentFolder.isEmpty()) { - appBar.setTitle(R.string.bookmarks); - } else { - appBar.setTitle(currentFolder); - } - } - - private void updateBookmarksListViewExcept(long[] exceptIdLongArray, String folderName) { - // Get a `Cursor` with the current contents of the bookmarks database except for the specified database IDs. - bookmarksCursor = bookmarksDatabaseHelper.getBookmarksCursorExcept(exceptIdLongArray, folderName); - - // Setup `bookmarksCursorAdapter` with `this` context. `false` disables autoRequery. - CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Get the favorite icon byte array from the cursor. - byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); - - // 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); - - // Display the bitmap in `bookmarkFavoriteIcon`. - ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon); - bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); - - - // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. - String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name); - bookmarkNameTextView.setText(bookmarkNameString); - - // Make the font bold for folders. - if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { - // The first argument is `null` because we don't want to change the font. - bookmarkNameTextView.setTypeface(null, Typeface.BOLD); - } else { // Reset the font to default. - bookmarkNameTextView.setTypeface(Typeface.DEFAULT); - } - } - }; - - // Update the `ListView`. - bookmarksListView.setAdapter(bookmarksCursorAdapter); - } - - private void deleteBookmarkFolderContents(int databaseId) { - // Get the name of the folder. - String folderName = bookmarksDatabaseHelper.getFolderName(databaseId); - - // Get the contents of the folder. - Cursor folderCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName); - - for (int i = 0; i < folderCursor.getCount(); i++) { - // Move `folderCursor` to the current row. - folderCursor.moveToPosition(i); - - // Get the database ID of the item. - int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID)); - - // If this is a folder, delete the contents first. - if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) { - deleteBookmarkFolderContents(itemDatabaseId); - } - - bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java new file mode 100644 index 00000000..b6f9bc61 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksActivity.java @@ -0,0 +1,900 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.design.widget.Snackbar; +import android.support.v4.app.NavUtils; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AppCompatDialogFragment; +import android.support.v7.widget.Toolbar; +import android.util.SparseBooleanArray; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.CursorAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.RadioButton; +import android.widget.TextView; + +import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog; +import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog; +import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog; +import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog; +import com.stoutner.privacybrowser.dialogs.MoveToFolderDialog; +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; + +import java.io.ByteArrayOutputStream; + +public class BookmarksActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, + MoveToFolderDialog.MoveToFolderListener { + + // `bookmarksDatabaseHelper` is public static so it can be accessed from `EditBookmarkDialog` and `MoveToFolderDialog`. It is also used in `onCreate()`, + // `onCreateBookmarkCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`. + public static BookmarksDatabaseHelper bookmarksDatabaseHelper; + + // `currentFolder` is public static so it can be accessed from `MoveToFolderDialog`. + // It is used in `onCreate`, `onOptionsItemSelected()`, `onCreateBookmarkCreate`, `onCreateBookmarkFolderCreate`, and `onEditBookmarkSave`. + public static String currentFolder; + + // `checkedItemIds` is public static so it can be accessed from `EditBookmarkDialog`, `EditBookmarkFolderDialog`, and `MoveToFolderDialog`. + // It is also used in `onActionItemClicked`. + public static long[] checkedItemIds; + + + // `bookmarksListView` is used in `onCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`. + private ListView bookmarksListView; + + // `contextualActionMode` is used in `onCreate()` and `onEditBookmarkSave()`. + private ActionMode contextualActionMode; + + // `selectedBookmarkPosition` is used in `onCreate()` and `onEditBookmarkSave()`. + private int selectedBookmarkPosition; + + // `appBar` is used in `onCreate()` and `updateBookmarksListView()`. + private ActionBar appBar; + + // `bookmarksCursor` is used in `onCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`. + private Cursor bookmarksCursor; + + // `oldFolderName` is used in `onCreate()` and `onEditBookmarkFolderSave()`. + private String oldFolderNameString; + + @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. + final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.bookmarks_toolbar); + setSupportActionBar(bookmarksAppBar); + + // Display the home arrow on `SupportActionBar`. + 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`. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. + // The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`. + bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0); + bookmarksListView = (ListView) findViewById(R.id.bookmarks_listview); + + // Set currentFolder to the home folder, which is `""` in the database. + currentFolder = ""; + + // Display the bookmarks in the ListView. + updateBookmarksListView(currentFolder); + + // 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 `Cursor` for this ID and move it to the first row. + Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID); + bookmarkCursor.moveToFirst(); + + // If the bookmark is a folder load its contents into the ListView. + if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { + // Update `currentFolder`. + currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + + // Reload the ListView with `currentFolder`. + updateBookmarksListView(currentFolder); + } else { // Load the URL into `mainWebView`. + // Get the bookmark URL and assign it to formattedUrlString. `mainWebView` will automatically reload when `BookmarksActivity` closes. + MainWebViewActivity.formattedUrlString = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)); + + NavUtils.navigateUpFromSameTask(bookmarksActivity); + } + + // Close the `Cursor`. + bookmarkCursor.close(); + } + }); + + // `MultiChoiceModeListener` handles long clicks. + bookmarksListView.setMultiChoiceModeListener(new AbsListView.MultiChoiceModeListener() { + // `moveBookmarkUpMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`. + MenuItem moveBookmarkUpMenuItem; + + // `moveBookmarkDownMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`. + MenuItem moveBookmarkDownMenuItem; + + // `editBookmarkMenuItem` is used in `onCreateActionMode()` and `onItemCheckedStateChanged`. + MenuItem editBookmarkMenuItem; + + // `selectAllBookmarks` is used in `onCreateActionMode()` and `onItemCheckedStateChanges`. + MenuItem selectAllBookmarksMenuItem; + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate the menu for the contextual app bar and set the title. + getMenuInflater().inflate(R.menu.bookmarks_context_menu, menu); + + // Set the title. + if (currentFolder.isEmpty()) { + // Use `R.string.bookmarks` if we are in the home folder. + mode.setTitle(R.string.bookmarks); + } else { // Use the current folder name as the title. + mode.setTitle(currentFolder); + } + + // Get a handle for MenuItems we need to selectively disable. + moveBookmarkUpMenuItem = menu.findItem(R.id.move_bookmark_up); + moveBookmarkDownMenuItem = menu.findItem(R.id.move_bookmark_down); + editBookmarkMenuItem = menu.findItem(R.id.edit_bookmark); + selectAllBookmarksMenuItem = menu.findItem(R.id.context_menu_select_all_bookmarks); + + // Get a handle for `contextualActionMode` so we can close it programatically. + contextualActionMode = mode; + + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + // Get an array of the selected bookmarks. + long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds(); + + // Calculate the number of selected bookmarks. + int numberOfSelectedBookmarks = selectedBookmarksLongArray.length; + + // Adjust the `mode` and the menu for the number of selected bookmarks. + if (numberOfSelectedBookmarks == 0) { + mode.finish(); + } else if (numberOfSelectedBookmarks == 1) { + // List the number of selected bookmarks in the subtitle. + mode.setSubtitle(getString(R.string.one_selected)); + + // Show the `Move Up`, `Move Down`, and `Edit` options. + moveBookmarkUpMenuItem.setVisible(true); + moveBookmarkDownMenuItem.setVisible(true); + editBookmarkMenuItem.setVisible(true); + + // Get the database IDs for the bookmarks. + int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0]; + int firstBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(0); + // bookmarksListView is 0 indexed. + int lastBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(bookmarksListView.getCount() - 1); + + // Disable `moveBookmarkUpMenuItem` if the selected bookmark is at the top of the ListView. + if (selectedBookmarkDatabaseId == firstBookmarkDatabaseId) { + moveBookmarkUpMenuItem.setEnabled(false); + moveBookmarkUpMenuItem.setIcon(R.drawable.move_bookmark_up_disabled); + } else { // Otherwise enable `moveBookmarkUpMenuItem`. + moveBookmarkUpMenuItem.setEnabled(true); + moveBookmarkUpMenuItem.setIcon(R.drawable.move_bookmark_up_enabled); + } + + // Disable `moveBookmarkDownMenuItem` if the selected bookmark is at the bottom of the ListView. + if (selectedBookmarkDatabaseId == lastBookmarkDatabaseId) { + moveBookmarkDownMenuItem.setEnabled(false); + moveBookmarkDownMenuItem.setIcon(R.drawable.move_bookmark_down_disabled); + } else { // Otherwise enable `moveBookmarkDownMenuItem`. + moveBookmarkDownMenuItem.setEnabled(true); + moveBookmarkDownMenuItem.setIcon(R.drawable.move_bookmark_down_enabled); + } + } else { // More than one bookmark is selected. + // List the number of selected bookmarks in the subtitle. + mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected)); + + // Hide non-applicable `MenuItems`. + moveBookmarkUpMenuItem.setVisible(false); + moveBookmarkDownMenuItem.setVisible(false); + editBookmarkMenuItem.setVisible(false); + } + + // Do not show `Select All` if all the bookmarks are already checked. + if (bookmarksListView.getCheckedItemIds().length == bookmarksListView.getCount()) { + selectAllBookmarksMenuItem.setVisible(false); + } else { + selectAllBookmarksMenuItem.setVisible(true); + } + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + int menuItemId = item.getItemId(); + + // `numberOfBookmarks` is used in `R.id.move_bookmark_up_enabled`, `R.id.move_bookmark_down_enabled`, and `R.id.context_menu_select_all_bookmarks`. + int numberOfBookmarks; + + // `selectedBookmarkLongArray` is used in `R.id.move_bookmark_up`, `R.id.move_bookmark_down`, and `R.id.edit_bookmark`. + long[]selectedBookmarkLongArray; + // `selectedBookmarkDatabaseId` is used in `R.id.move_bookmark_up`, `R.id.move_bookmark_down`, and `R.id.edit_bookmark`. + int selectedBookmarkDatabaseId; + // `selectedBookmarkNewPosition` is used in `R.id.move_bookmark_up` and `R.id.move_bookmark_down`. + int selectedBookmarkNewPosition; + // `bookmarkPositionSparseBooleanArray` is used in `R.id.edit_bookmark` and `R.id.delete_bookmark`. + SparseBooleanArray bookmarkPositionSparseBooleanArray; + + switch (menuItemId) { + case R.id.move_bookmark_up: + // Get the selected bookmark database ID. + selectedBookmarkLongArray = bookmarksListView.getCheckedItemIds(); + selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; + + // Initialize `selectedBookmarkNewPosition`. + selectedBookmarkNewPosition = 0; + + for (int i = 0; i < bookmarksListView.getCount(); i++) { + int databaseId = (int) bookmarksListView.getItemIdAtPosition(i); + int nextBookmarkDatabaseId = (int) bookmarksListView.getItemIdAtPosition(i + 1); + + if (databaseId == selectedBookmarkDatabaseId || nextBookmarkDatabaseId == selectedBookmarkDatabaseId) { + if (databaseId == selectedBookmarkDatabaseId) { + // Move the selected bookmark up one and store the new bookmark position. + bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i - 1); + selectedBookmarkNewPosition = i - 1; + } else { // Move the bookmark above the selected bookmark down one. + bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i + 1); + } + } else { + // Reset the rest of the bookmarks' DISPLAY_ORDER to match the position in the ListView. + // This isn't necessary, but it clears out any stray values that might have crept into the database. + bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i); + } + } + + // Refresh the ListView. + updateBookmarksListView(currentFolder); + + // Select the previously selected bookmark in the new location. + bookmarksListView.setItemChecked(selectedBookmarkNewPosition, true); + + // Scroll `bookmarksListView` to five items above `selectedBookmarkNewPosition`. + bookmarksListView.setSelection(selectedBookmarkNewPosition - 5); + + break; + + case R.id.move_bookmark_down: + // Get the selected bookmark database ID. + selectedBookmarkLongArray = bookmarksListView.getCheckedItemIds(); + selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; + + // Initialize `selectedBookmarkNewPosition`. + selectedBookmarkNewPosition = 0; + + for (int i = 0; i 0)) { + String cannotCreateFolder = getResources().getString(R.string.cannot_create_folder) + " \"" + folderNameString + "\""; + Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), cannotCreateFolder, Snackbar.LENGTH_INDEFINITE).show(); + } else { // Create the folder. + // Get the new folder icon `Bitmap`. + RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton); + Bitmap folderIconBitmap; + if (defaultFolderIconRadioButton.isChecked()) { + // Get the default folder icon `ImageView` from the `Dialog` and convert it to a `Bitmap`. + ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon); + Drawable folderIconDrawable = folderIconImageView.getDrawable(); + BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + folderIconBitmap = folderIconBitmapDrawable.getBitmap(); + } else { // Assign `favoriteIcon` from the `WebView`. + folderIconBitmap = MainWebViewActivity.favoriteIcon; + } + + // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); + folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); + byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); + + // Move all the bookmarks down one in the display order. + for (int i = 0; i < bookmarksListView.getCount(); i++) { + int databaseId = (int) bookmarksListView.getItemIdAtPosition(i); + bookmarksDatabaseHelper.updateBookmarkDisplayOrder(databaseId, i + 1); + } + + // Create the folder, placing it at the top of the ListView + bookmarksDatabaseHelper.createFolder(folderNameString, 0, currentFolder, folderIconByteArray); + + // Refresh the ListView. + updateBookmarksListView(currentFolder); + } + } + + @Override + public void onSaveEditBookmark(AppCompatDialogFragment dialogFragment) { + // Get a long array with the the databaseId of the selected bookmark and convert it to an `int`. + long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds(); + int selectedBookmarkDatabaseId = (int) selectedBookmarksLongArray[0]; + + // Get the `EditText`s from the `editBookmarkDialogFragment` and extract the strings. + EditText editBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext); + String bookmarkNameString = editBookmarkNameEditText.getText().toString(); + EditText editBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext); + String bookmarkUrlString = editBookmarkUrlEditText.getText().toString(); + + // Get `edit_bookmark_current_icon_radiobutton`. + RadioButton currentBookmarkIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton); + + if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon. + bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString); + } else { // Update the bookmark using the `WebView` favorite icon. + ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream(); + MainWebViewActivity.favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream); + byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray(); + + // Update the bookmark and the favorite icon. + bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray); + } + + // Close the contextual action mode. + contextualActionMode.finish(); + + // Refresh the `ListView`. `setSelection` scrolls to the position of the bookmark that was edited. + updateBookmarksListView(currentFolder); + bookmarksListView.setSelection(selectedBookmarkPosition); + } + + @Override + public void onSaveEditBookmarkFolder(AppCompatDialogFragment dialogFragment) { + // Get the new folder name. + EditText editFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext); + String newFolderNameString = editFolderNameEditText.getText().toString(); + + // Check to see if the new folder name is unique. + Cursor bookmarkFolderCursor = bookmarksDatabaseHelper.getFolderCursor(newFolderNameString); + int existingFoldersWithNewName = bookmarkFolderCursor.getCount(); + bookmarkFolderCursor.close(); + if ( ((existingFoldersWithNewName == 0) || newFolderNameString.equals(oldFolderNameString)) && !newFolderNameString.isEmpty()) { + // Get a long array with the the database ID of the selected folder and convert it to an `int`. + long[] selectedFolderLongArray = bookmarksListView.getCheckedItemIds(); + int selectedFolderDatabaseId = (int) selectedFolderLongArray[0]; + + // Get the `RadioButtons` from the `Dialog`. + RadioButton currentFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton); + RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton); + + // Check if the favorite icon has changed. + if (currentFolderIconRadioButton.isChecked()) { + // Update the folder name if it has changed without modifying the favorite icon. + if (!newFolderNameString.equals(oldFolderNameString)) { + bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString); + + // Refresh the `ListView`. `setSelection` scrolls to the position of the folder that was edited. + updateBookmarksListView(currentFolder); + bookmarksListView.setSelection(selectedBookmarkPosition); + } + } else { // Update the folder icon. + // Get the new folder icon `Bitmap`. + Bitmap folderIconBitmap; + if (defaultFolderIconRadioButton.isChecked()) { + // Get the default folder icon `ImageView` from the `Drawable` and convert it to a `Bitmap`. + ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon); + Drawable folderIconDrawable = folderIconImageView.getDrawable(); + BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable; + folderIconBitmap = folderIconBitmapDrawable.getBitmap(); + } else { // Get the web page icon `ImageView` from the `Dialog`. + folderIconBitmap = MainWebViewActivity.favoriteIcon; + } + + // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream(); + folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream); + byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray(); + + bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray); + + // Refresh the `ListView`. `setSelection` scrolls to the position of the folder that was edited. + updateBookmarksListView(currentFolder); + bookmarksListView.setSelection(selectedBookmarkPosition); + } + } else { // Don't edit the folder because the new name is not unique. + String cannot_rename_folder = getResources().getString(R.string.cannot_save_folder) + " \"" + newFolderNameString + "\""; + Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), cannot_rename_folder, Snackbar.LENGTH_INDEFINITE).show(); + } + + // Close the contextual action mode. + contextualActionMode.finish(); + } + + @Override + public void onMoveToFolder(AppCompatDialogFragment dialogFragment) { + // Get the new folder database id. + ListView folderListView = (ListView) dialogFragment.getDialog().findViewById(R.id.move_to_folder_listview); + long[] newFolderLongArray = folderListView.getCheckedItemIds(); + + if (newFolderLongArray.length == 0) { // No new folder was selected. + Snackbar.make(findViewById(R.id.bookmarks_coordinatorlayout), getString(R.string.cannot_move_bookmarks), Snackbar.LENGTH_INDEFINITE).show(); + } else { // Move the selected bookmarks. + // Get the new folder database ID. + int newFolderDatabaseId = (int) newFolderLongArray[0]; + + // Instantiate `newFolderName`. + String newFolderName; + + if (newFolderDatabaseId == 0) { + // The new folder is the home folder, represented as `""` in the database. + newFolderName = ""; + } else { + // Get the new folder name from the database. + newFolderName = bookmarksDatabaseHelper.getFolderName(newFolderDatabaseId); + } + + // Get a long array with the the database ID of the selected bookmarks. + long[] selectedBookmarksLongArray = bookmarksListView.getCheckedItemIds(); + for (long databaseIdLong : selectedBookmarksLongArray) { + // Get `databaseIdInt` for each selected bookmark. + int databaseIdInt = (int) databaseIdLong; + + // Move the selected bookmark to the new folder. + bookmarksDatabaseHelper.moveToFolder(databaseIdInt, newFolderName); + } + + // Refresh the `ListView`. + updateBookmarksListView(currentFolder); + + // Close the contextual app bar. + contextualActionMode.finish(); + } + } + + private void updateBookmarksListView(String folderName) { + // Get a `Cursor` with the current contents of the bookmarks database. + bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName); + + // Setup `bookmarksCursorAdapter` with `this` context. `false` disables `autoRequery`. + CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // Inflate the individual item layout. `false` does not attach it to the root. + return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Get the favorite icon byte array from the `Cursor`. + byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + + // 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); + + // Display the bitmap in `bookmarkFavoriteIcon`. + ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon); + bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); + + + // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. + String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name); + bookmarkNameTextView.setText(bookmarkNameString); + + // Make the font bold for folders. + if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { + bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD); + } else { // Reset the font to default for normal bookmarks. + bookmarkNameTextView.setTypeface(Typeface.DEFAULT); + } + } + }; + + // Update the ListView. + bookmarksListView.setAdapter(bookmarksCursorAdapter); + + // Set the AppBar title. + if (currentFolder.isEmpty()) { + appBar.setTitle(R.string.bookmarks); + } else { + appBar.setTitle(currentFolder); + } + } + + private void updateBookmarksListViewExcept(long[] exceptIdLongArray, String folderName) { + // Get a `Cursor` with the current contents of the bookmarks database except for the specified database IDs. + bookmarksCursor = bookmarksDatabaseHelper.getBookmarksCursorExcept(exceptIdLongArray, folderName); + + // Setup `bookmarksCursorAdapter` with `this` context. `false` disables autoRequery. + CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // Inflate the individual item layout. `false` does not attach it to the root. + return getLayoutInflater().inflate(R.layout.bookmarks_item_linearlayout, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Get the favorite icon byte array from the cursor. + byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + + // 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); + + // Display the bitmap in `bookmarkFavoriteIcon`. + ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon); + bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); + + + // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`. + String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name); + bookmarkNameTextView.setText(bookmarkNameString); + + // Make the font bold for folders. + if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { + // The first argument is `null` because we don't want to change the font. + bookmarkNameTextView.setTypeface(null, Typeface.BOLD); + } else { // Reset the font to default. + bookmarkNameTextView.setTypeface(Typeface.DEFAULT); + } + } + }; + + // Update the `ListView`. + bookmarksListView.setAdapter(bookmarksCursorAdapter); + } + + private void deleteBookmarkFolderContents(int databaseId) { + // Get the name of the folder. + String folderName = bookmarksDatabaseHelper.getFolderName(databaseId); + + // Get the contents of the folder. + Cursor folderCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(folderName); + + for (int i = 0; i < folderCursor.getCount(); i++) { + // Move `folderCursor` to the current row. + folderCursor.moveToPosition(i); + + // Get the database ID of the item. + int itemDatabaseId = folderCursor.getInt(folderCursor.getColumnIndex(BookmarksDatabaseHelper._ID)); + + // If this is a folder, delete the contents first. + if (bookmarksDatabaseHelper.isFolder(itemDatabaseId)) { + deleteBookmarkFolderContents(itemDatabaseId); + } + + bookmarksDatabaseHelper.deleteBookmark(itemDatabaseId); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseView.java b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseView.java deleted file mode 100644 index 44f23bfc..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseView.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.content.Context; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Typeface; -import android.os.Bundle; -import android.support.v4.content.ContextCompat; -import android.support.v4.widget.CursorAdapter; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; - -public class BookmarksDatabaseView extends AppCompatActivity { - // `bookmarksDatabaseHelper` is used in `onCreate()` and `updateBookmarksListView()`. - private BookmarksDatabaseHelper bookmarksDatabaseHelper; - - // `bookmarksListView` is used in `onCreate()` and `updateBookmarksListView()`. - private ListView bookmarksListView; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.bookmarks_database_view_coordinatorlayout); - - // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. - final Toolbar bookmarksDatabaseViewAppBar = (Toolbar) findViewById(R.id.bookmarks_database_view_toolbar); - setSupportActionBar(bookmarksDatabaseViewAppBar); - - // Display the home arrow on `SupportActionBar`. - 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. - // `this` specifies the context. The two `null`s do not specify the database name or a `CursorFactory`. - // The `0` is to specify a database version, but that is set instead using a constant in `BookmarksDatabaseHelper`. - bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0); - bookmarksListView = (ListView) findViewById(R.id.bookmarks_database_view_listview); - - // Display the bookmarks in the ListView. - updateBookmarksListView(); - - } - - private void updateBookmarksListView() { - // Get a `Cursor` with the current contents of the bookmarks database. - final Cursor bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursor(); - - // Setup `bookmarksCursorAdapter` with `this` context. The `false` disables autoRequery. - CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getLayoutInflater().inflate(R.layout.bookmarks_database_view_item_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - boolean isFolder = (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1); - - // Get the database ID from the `Cursor` and display it in `bookmarkDatabaseIdTextView`. - int bookmarkDatabaseId = cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper._ID)); - TextView bookmarkDatabaseIdTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_database_id); - bookmarkDatabaseIdTextView.setText(String.valueOf(bookmarkDatabaseId)); - - // Get the favorite icon byte array from the `Cursor`. - byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); - // Convert the byte array to a `Bitmap` beginning at the beginning at the first byte and ending at the last. - Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length); - // Display the bitmap in `bookmarkFavoriteIcon`. - ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmarks_database_view_favorite_icon); - bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); - - // Get the bookmark name from the `Cursor` and display it in `bookmarkNameTextView`. - String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_bookmark_name); - bookmarkNameTextView.setText(bookmarkNameString); - // Make the font bold for folders. - if (isFolder) { - // The first argument is `null` because we don't want to change the font. - bookmarkNameTextView.setTypeface(null, Typeface.BOLD); - } else { // Reset the font to default. - bookmarkNameTextView.setTypeface(Typeface.DEFAULT); - } - - // Get the display order from the `Cursor` and display it in `bookmarkDisplayOrderTextView`. - int bookmarkDisplayOrder = cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)); - TextView bookmarkDisplayOrderTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_display_order); - bookmarkDisplayOrderTextView.setText(String.valueOf(bookmarkDisplayOrder)); - - // Get the parent folder from the `Cursor` and display it in `bookmarkParentFolder`. - String bookmarkParentFolder = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.PARENT_FOLDER)); - ImageView parentFolderImageView = (ImageView) view.findViewById(R.id.bookmarks_database_view_parent_folder_icon); - TextView bookmarkParentFolderTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_parent_folder); - // Make the folder name gray if it is the home folder. - if (bookmarkParentFolder.isEmpty()) { - parentFolderImageView.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.folder_gray)); - bookmarkParentFolderTextView.setText(R.string.home_folder); - bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.gray_500)); - } else { - parentFolderImageView.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.folder_dark_blue)); - bookmarkParentFolderTextView.setText(bookmarkParentFolder); - bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.black)); - } - - // Get the bookmark URL form the `Cursor` and display it in `bookmarkUrlTextView`. - String bookmarkUrlString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)); - TextView bookmarkUrlTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_bookmark_url); - bookmarkUrlTextView.setText(bookmarkUrlString); - if (isFolder) { - bookmarkUrlTextView.setVisibility(View.GONE); - } else { - bookmarkUrlTextView.setVisibility(View.VISIBLE); - } - } - }; - - // Update the ListView. - bookmarksListView.setAdapter(bookmarksCursorAdapter); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java new file mode 100644 index 00000000..323c1099 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseViewActivity.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.CursorAdapter; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; + +public class BookmarksDatabaseViewActivity extends AppCompatActivity { + // `bookmarksDatabaseHelper` is used in `onCreate()` and `updateBookmarksListView()`. + private BookmarksDatabaseHelper bookmarksDatabaseHelper; + + // `bookmarksListView` is used in `onCreate()` and `updateBookmarksListView()`. + private ListView bookmarksListView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.bookmarks_database_view_coordinatorlayout); + + // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. + final Toolbar bookmarksDatabaseViewAppBar = (Toolbar) findViewById(R.id.bookmarks_database_view_toolbar); + setSupportActionBar(bookmarksDatabaseViewAppBar); + + // Display the home arrow on `SupportActionBar`. + 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. + // `this` specifies the context. The two `null`s do not specify the database name or a `CursorFactory`. + // The `0` is to specify a database version, but that is set instead using a constant in `BookmarksDatabaseHelper`. + bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0); + bookmarksListView = (ListView) findViewById(R.id.bookmarks_database_view_listview); + + // Display the bookmarks in the ListView. + updateBookmarksListView(); + + } + + private void updateBookmarksListView() { + // Get a `Cursor` with the current contents of the bookmarks database. + final Cursor bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursor(); + + // Setup `bookmarksCursorAdapter` with `this` context. The `false` disables autoRequery. + CursorAdapter bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // Inflate the individual item layout. `false` does not attach it to the root. + return getLayoutInflater().inflate(R.layout.bookmarks_database_view_item_linearlayout, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + boolean isFolder = (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1); + + // Get the database ID from the `Cursor` and display it in `bookmarkDatabaseIdTextView`. + int bookmarkDatabaseId = cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper._ID)); + TextView bookmarkDatabaseIdTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_database_id); + bookmarkDatabaseIdTextView.setText(String.valueOf(bookmarkDatabaseId)); + + // Get the favorite icon byte array from the `Cursor`. + byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + // Convert the byte array to a `Bitmap` beginning at the beginning at the first byte and ending at the last. + Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length); + // Display the bitmap in `bookmarkFavoriteIcon`. + ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmarks_database_view_favorite_icon); + bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap); + + // Get the bookmark name from the `Cursor` and display it in `bookmarkNameTextView`. + String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_bookmark_name); + bookmarkNameTextView.setText(bookmarkNameString); + // Make the font bold for folders. + if (isFolder) { + // The first argument is `null` because we don't want to change the font. + bookmarkNameTextView.setTypeface(null, Typeface.BOLD); + } else { // Reset the font to default. + bookmarkNameTextView.setTypeface(Typeface.DEFAULT); + } + + // Get the display order from the `Cursor` and display it in `bookmarkDisplayOrderTextView`. + int bookmarkDisplayOrder = cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.DISPLAY_ORDER)); + TextView bookmarkDisplayOrderTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_display_order); + bookmarkDisplayOrderTextView.setText(String.valueOf(bookmarkDisplayOrder)); + + // Get the parent folder from the `Cursor` and display it in `bookmarkParentFolder`. + String bookmarkParentFolder = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.PARENT_FOLDER)); + ImageView parentFolderImageView = (ImageView) view.findViewById(R.id.bookmarks_database_view_parent_folder_icon); + TextView bookmarkParentFolderTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_parent_folder); + // Make the folder name gray if it is the home folder. + if (bookmarkParentFolder.isEmpty()) { + parentFolderImageView.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.folder_gray)); + bookmarkParentFolderTextView.setText(R.string.home_folder); + bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.gray_500)); + } else { + parentFolderImageView.setImageDrawable(ContextCompat.getDrawable(getApplicationContext(), R.drawable.folder_dark_blue)); + bookmarkParentFolderTextView.setText(bookmarkParentFolder); + bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.black)); + } + + // Get the bookmark URL form the `Cursor` and display it in `bookmarkUrlTextView`. + String bookmarkUrlString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)); + TextView bookmarkUrlTextView = (TextView) view.findViewById(R.id.bookmarks_database_view_bookmark_url); + bookmarkUrlTextView.setText(bookmarkUrlString); + if (isFolder) { + bookmarkUrlTextView.setVisibility(View.GONE); + } else { + bookmarkUrlTextView.setVisibility(View.VISIBLE); + } + } + }; + + // Update the ListView. + bookmarksListView.setAdapter(bookmarksCursorAdapter); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainSettingsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainSettingsActivity.java new file mode 100644 index 00000000..ae3afa96 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainSettingsActivity.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.fragments.DomainSettingsFragment; + +public class DomainSettingsActivity extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.domain_settings_coordinatorlayout); + + // We ned to use `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. + Toolbar domainSettingsAppBar = (Toolbar) findViewById(R.id.domain_settings_toolbar); + setSupportActionBar(domainSettingsAppBar); + + // Display the home arrow on `appBar`. + final ActionBar appBar = getSupportActionBar(); + assert appBar != null; // This assert removes the incorrect lint warning in Android Studio on the following line that `appBar` might be `null`. + appBar.setDisplayHomeAsUpEnabled(true); + + // Get the intent that started the activity. + final Intent launchingIntent = getIntent(); + + // Extract the `databaseID`. The default value is `0`. + int databaseId = launchingIntent.getIntExtra(DomainSettingsFragment.DATABASE_ID, 0); + + // Store `databaseId` in `argumentsBundle`. + Bundle argumentsBundle = new Bundle(); + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseId); + + // Add `argumentsBundle` to `domainSettingsFragment`. + DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); + domainSettingsFragment.setArguments(argumentsBundle); + + // Display `domainSettingsFragment`. + getSupportFragmentManager().beginTransaction().replace(R.id.domain_settings_linearlayout, domainSettingsFragment).commit(); + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java new file mode 100644 index 00000000..0cc4d506 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java @@ -0,0 +1,161 @@ +/* + * Copyright 2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.app.AppCompatDialogFragment; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.CursorAdapter; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.dialogs.AddDomainDialog; +import com.stoutner.privacybrowser.fragments.DomainSettingsFragment; +import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; + +public class DomainsActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener { + // `domainsDatabaseHelper` is used in `onCreate()`, `onAddDomain()`, and `updateDomainsRecyclerView()`. + private static DomainsDatabaseHelper domainsDatabaseHelper; + + // `domainsRecyclerView` is used in `onCreate()` and `updateDomainsListView()`. + private ListView domainsListView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.domains_coordinatorlayout); + + // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. + final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.domains_toolbar); + setSupportActionBar(bookmarksAppBar); + + // Display the home arrow on `SupportActionBar`. + 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. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. + // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. + domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); + + // Determine if we are in two pane mode. `domains_settings_linearlayout` is only populated if two panes are present. + final boolean twoPaneMode = ((findViewById(R.id.domain_settings_linearlayout)) != null); + + // Initialize `domainsListView`. + domainsListView = (ListView) findViewById(R.id.domains_listview); + + domainsListView.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 domains database. + int databaseId = (int) id; + + // Display the Domain Settings. + if (twoPaneMode) { // Display a fragment in two paned mode. + // Highlight the selected domain. + domainsListView.setItemChecked(position, true); + + // Store `databaseId` in `argumentsBundle`. + Bundle argumentsBundle = new Bundle(); + argumentsBundle.putInt(DomainSettingsFragment.DATABASE_ID, databaseId); + + // Add `argumentsBundle` to `domainSettingsFragment`. + DomainSettingsFragment domainSettingsFragment = new DomainSettingsFragment(); + domainSettingsFragment.setArguments(argumentsBundle); + + // Display `domainSettingsFragment`. + getSupportFragmentManager().beginTransaction().replace(R.id.domain_settings_linearlayout, domainSettingsFragment).commit(); + } else { // Load the second activity on smaller screens. + // Get a handle for the context. + Context context = view.getContext(); + + // Create `domainSettingsActivityIntent` with the `databaseId`. + Intent domainSettingsActivityIntent = new Intent(context, DomainSettingsActivity.class); + domainSettingsActivityIntent.putExtra(DomainSettingsFragment.DATABASE_ID, databaseId); + + // Start `DomainSettingsActivity`. + context.startActivity(domainSettingsActivityIntent); + } + } + }); + + FloatingActionButton addDomainFAB = (FloatingActionButton) findViewById(R.id.add_domain_fab); + addDomainFAB.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + // Show the `AddDomainDialog` `AlertDialog` and name the instance `@string/add_domain`. + AppCompatDialogFragment addDomainDialog = new AddDomainDialog(); + addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain)); + } + }); + + // Load the `ListView`. + updateDomainsListView(); + } + + @Override + public void onAddDomain(AppCompatDialogFragment dialogFragment) { + // Get the `domainNameEditText` from `dialogFragment` and extract the string. + EditText domainNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.domain_name_edittext); + String domainNameString = domainNameEditText.getText().toString(); + + // Create the domain. + domainsDatabaseHelper.addDomain(domainNameString); + + // Refresh the `ListView`. + updateDomainsListView(); + } + + private void updateDomainsListView() { + // Get a `Cursor` with the current contents of the domains database. + Cursor domainsCursor = domainsDatabaseHelper.getCursorOrderedByDomain(); + + // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`. + CursorAdapter domainsCursorAdapter = new CursorAdapter(this, domainsCursor, false) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // Inflate the individual item layout. `false` does not attach it to the root. + return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Set the domain name. + String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN)); + TextView domainNameTextView = (TextView) view.findViewById(R.id.domain_name_textview); + domainNameTextView.setText(domainNameString); + } + }; + + // Update the `RecyclerView`. + domainsListView.setAdapter(domainsCursorAdapter); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsList.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsList.java deleted file mode 100644 index fb994f41..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsList.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.content.Context; -import android.database.Cursor; -import android.os.Bundle; -import android.support.design.widget.FloatingActionButton; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.app.AppCompatDialogFragment; -import android.support.v7.widget.Toolbar; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.CursorAdapter; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.dialogs.AddDomain; -import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; - -public class DomainsList extends AppCompatActivity implements AddDomain.AddDomainListener { - // `domainsDatabaseHelper` is used in `onCreate()`, `onAddDomain()`, and `updateDomainsRecyclerView()`. - private static DomainsDatabaseHelper domainsDatabaseHelper; - - // `domainsRecyclerView` is used in `onCreate()` and `updateDomainsListView()`. - private ListView domainsListView; - - private boolean twoPaneMode; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.domains_list_coordinatorlayout); - - // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. - final Toolbar bookmarksAppBar = (Toolbar) findViewById(R.id.domains_toolbar); - setSupportActionBar(bookmarksAppBar); - - // Display the home arrow on `SupportActionBar`. - 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. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. - // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. - domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0); - - // Determine if we are in two pane mode. `domains_list_framelayout` is only populated if two panes are present. - twoPaneMode = ((findViewById(R.id.domains_list_framelayout)) != null); - - // Initialize `domainsListView`. - domainsListView = (ListView) findViewById(R.id.domains_listview); - - domainsListView.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 domains database. - int databaseId = (int) id; - - // Get the database `Cursor` for this ID and move it to the first row. - Cursor domainCursor = domainsDatabaseHelper.getCursorForId(databaseId); - domainCursor.moveToFirst(); - - // If the - } - }); - - FloatingActionButton addDomainFAB = (FloatingActionButton) findViewById(R.id.add_domain_fab); - addDomainFAB.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - // Show the `AddDomain` `AlertDialog` and name the instance `@string/add_domain`. - AppCompatDialogFragment addDomainDialog = new AddDomain(); - addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain)); - } - }); - - // Load the `ListView`. - updateDomainsListView(); - } - - @Override - public void onAddDomain(AppCompatDialogFragment dialogFragment) { - // Get the `domainNameEditText` from `dialogFragment` and extract the string. - EditText domainNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.domain_name_edittext); - String domainNameString = domainNameEditText.getText().toString(); - - // Create the domain. - domainsDatabaseHelper.addDomain(domainNameString); - - // Refresh the `ListView`. - updateDomainsListView(); - } - - private void updateDomainsListView() { - // Get a `Cursor` with the current contents of the domains database. - Cursor domainsCursor = domainsDatabaseHelper.getCursorOrderedByDomain(); - - // Setup `domainsCursorAdapter` with `this` context. `false` disables `autoRequery`. - CursorAdapter domainsCursorAdapter = new CursorAdapter(this, domainsCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getLayoutInflater().inflate(R.layout.domain_name_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Set the domain name. - String domainNameString = cursor.getString(cursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN)); - TextView domainNameTextView = (TextView) view.findViewById(R.id.domain_name_textview); - domainNameTextView.setText(domainNameString); - } - }; - - // Update the `RecyclerView`. - domainsListView.setAdapter(domainsCursorAdapter); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/Guide.java b/app/src/main/java/com/stoutner/privacybrowser/activities/Guide.java deleted file mode 100644 index 2861f0e3..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/Guide.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright 2016 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; - -import com.stoutner.privacybrowser.fragments.GuideTab; -import com.stoutner.privacybrowser.R; - -public class Guide extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.guide_coordinatorlayout); - - // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21. - Toolbar guideAppBar = (Toolbar) findViewById(R.id.guide_toolbar); - setSupportActionBar(guideAppBar); - - // 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); - - // Setup the ViewPager. - ViewPager aboutViewPager = (ViewPager) findViewById(R.id.guide_viewpager); - assert aboutViewPager != null; // This assert removes the incorrect warning in Android Studio on the following line that aboutViewPager might be null. - aboutViewPager.setAdapter(new guidePagerAdapter(getSupportFragmentManager())); - - // Setup the TabLayout and connect it to the ViewPager. - TabLayout aboutTabLayout = (TabLayout) findViewById(R.id.guide_tablayout); - assert aboutTabLayout != null; // This assert removes the incorrect warning in Android Studio on the following line that aboutTabLayout might be null. - aboutTabLayout.setupWithViewPager(aboutViewPager); - } - - public class guidePagerAdapter extends FragmentPagerAdapter { - private guidePagerAdapter(FragmentManager fm) { - super(fm); - } - - @Override - // Get the count of the number of tabs. - public int getCount() { - return 8; - } - - @Override - // Get the name of each tab. Tab numbers start at 0. - public CharSequence getPageTitle(int tab) { - switch (tab) { - case 0: - return getString(R.string.overview); - - case 1: - return getString(R.string.javascript); - - case 2: - return getString(R.string.local_storage); - - case 3: - return getString(R.string.user_agent); - - case 4: - return getString(R.string.tor); - - case 5: - return getString(R.string.tracking_ids); - - case 6: - return getString(R.string.clear_and_exit); - - case 7: - return getString(R.string.planned_features); - - default: - return ""; - } - } - - @Override - // Setup each tab. - public Fragment getItem(int tab) { - return GuideTab.createTab(tab); - } - } - -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.java new file mode 100644 index 00000000..c0caeabc --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/GuideActivity.java @@ -0,0 +1,112 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import com.stoutner.privacybrowser.fragments.GuideTabFragment; +import com.stoutner.privacybrowser.R; + +public class GuideActivity extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.guide_coordinatorlayout); + + // We need to use `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. + Toolbar guideAppBar = (Toolbar) findViewById(R.id.guide_toolbar); + setSupportActionBar(guideAppBar); + + // Display the home arrow on `ppBar`. + final ActionBar appBar = getSupportActionBar(); + assert appBar != null; // This assert removes the incorrect lint warning in Android Studio on the following line that `appBar` might be `null`. + appBar.setDisplayHomeAsUpEnabled(true); + + // Setup the ViewPager. + ViewPager aboutViewPager = (ViewPager) findViewById(R.id.guide_viewpager); + assert aboutViewPager != null; // This assert removes the incorrect warning in Android Studio on the following line that aboutViewPager might be null. + aboutViewPager.setAdapter(new guidePagerAdapter(getSupportFragmentManager())); + + // Setup the TabLayout and connect it to the ViewPager. + TabLayout aboutTabLayout = (TabLayout) findViewById(R.id.guide_tablayout); + assert aboutTabLayout != null; // This assert removes the incorrect warning in Android Studio on the following line that aboutTabLayout might be null. + aboutTabLayout.setupWithViewPager(aboutViewPager); + } + + private class guidePagerAdapter extends FragmentPagerAdapter { + private guidePagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + // Get the count of the number of tabs. + public int getCount() { + return 8; + } + + @Override + // Get the name of each tab. Tab numbers start at 0. + public CharSequence getPageTitle(int tab) { + switch (tab) { + case 0: + return getString(R.string.overview); + + case 1: + return getString(R.string.javascript); + + case 2: + return getString(R.string.local_storage); + + case 3: + return getString(R.string.user_agent); + + case 4: + return getString(R.string.tor); + + case 5: + return getString(R.string.tracking_ids); + + case 6: + return getString(R.string.clear_and_exit); + + case 7: + return getString(R.string.planned_features); + + default: + return ""; + } + } + + @Override + // Setup each tab. + public Fragment getItem(int tab) { + return GuideTabFragment.createTab(tab); + } + } + +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java deleted file mode 100644 index e6e5304a..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java +++ /dev/null @@ -1,2053 +0,0 @@ -/* - * Copyright 2015-2017 Soren Stoutner . - * - * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.annotation.SuppressLint; -import android.app.DialogFragment; -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.print.PrintDocumentAdapter; -import android.print.PrintManager; -import android.support.annotation.NonNull; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.NavigationView; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -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.text.Editable; -import android.text.TextWatcher; -import android.util.Patterns; -import android.view.ContextMenu; -import android.view.GestureDetector; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; -import android.webkit.CookieManager; -import android.webkit.DownloadListener; -import android.webkit.SslErrorHandler; -import android.webkit.WebBackForwardList; -import android.webkit.WebChromeClient; -import android.webkit.WebResourceResponse; -import android.webkit.WebStorage; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.webkit.WebViewDatabase; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import com.stoutner.privacybrowser.BannerAd; -import com.stoutner.privacybrowser.BuildConfig; -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.helpers.OrbotProxyHelper; -import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcut; -import com.stoutner.privacybrowser.dialogs.DownloadFile; -import com.stoutner.privacybrowser.dialogs.DownloadImage; -import com.stoutner.privacybrowser.dialogs.SslCertificateError; -import com.stoutner.privacybrowser.dialogs.UrlHistory; -import com.stoutner.privacybrowser.dialogs.ViewSslCertificate; - -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -// 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 MainWebView extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener, - SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener, DownloadImage.DownloadImageListener, UrlHistory.UrlHistoryListener { - - // `appBar` is public static so it can be accessed from `OrbotProxyHelper`. - // It is also used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applySettings()`. - public static ActionBar appBar; - - // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `Bookmarks`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`. - // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`. - public static Bitmap favoriteIcon; - - // `formattedUrlString` is public static so it can be accessed from `Bookmarks`. - // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`. - public static String formattedUrlString; - - // `sslCertificate` is public static so it can be accessed from `ViewSslCertificate`. It is also used in `onCreate()`. - public static SslCertificate sslCertificate; - - // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`. - public static String orbotStatus; - - - // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`. - private DrawerLayout drawerLayout; - - // `rootCoordinatorLayout` is used in `onCreate()` and `applySettings()`. - private CoordinatorLayout rootCoordinatorLayout; - - // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`. - private WebView mainWebView; - - // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. - private FrameLayout fullScreenVideoFrameLayout; - - // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`. - private SwipeRefreshLayout swipeRefreshLayout; - - // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`. - private CookieManager cookieManager; - - // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrlFromTextBox()`. - private final Map customHeaders = new HashMap<>(); - - // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`. - // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`. - private Boolean javaScriptEnabled; - - // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applySettings()`. - private boolean firstPartyCookiesEnabled; - - // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`. - private boolean thirdPartyCookiesEnabled; - - // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`. - private boolean domStorageEnabled; - - // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`. - private boolean saveFormDataEnabled; - - // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`. - private boolean swipeToRefreshEnabled; - - // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`. - private String homepage; - - // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`. - private String javaScriptDisabledSearchURL; - - // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`. - private String javaScriptEnabledSearchURL; - - // `adBlockerEnabled` is used in `onCreate()` and `applySettings()`. - private boolean adBlockerEnabled; - - // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applySettings()`. - private boolean fullScreenBrowsingModeEnabled; - - // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applySettings()`. - private boolean inFullScreenBrowsingMode; - - // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applySettings()`. - private boolean hideSystemBarsOnFullscreen; - - // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applySettings()`. - private boolean translucentNavigationBarOnFullscreen; - - // `proxyThroughOrbot` is used in `onCreate()` and `applySettings()` - private boolean proxyThroughOrbot; - - // `pendingUrl` is used in `onCreate()` and `applySettings()` - private static String pendingUrl; - - // `waitingForOrbotData` is used in `onCreate()` and `applySettings()`. - private String waitingForOrbotHTMLString; - - // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. - private LinearLayout findOnPageLinearLayout; - - // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. - private EditText findOnPageEditText; - - // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`. - private Menu mainMenu; - - // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`. - private ActionBarDrawerToggle drawerToggle; - - // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. - private Toolbar supportAppBar; - - // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`. - private EditText urlTextBox; - - // `adView` is used in `onCreate()` and `onConfigurationChanged()`. - private View adView; - - // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`. - private SslErrorHandler sslErrorHandler; - - // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`. - private InputMethodManager inputMethodManager; - - // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`. - private RelativeLayout mainWebViewRelativeLayout; - - @Override - // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers. - @SuppressLint("SetJavaScriptEnabled") - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.drawerlayout); - - // Get a handle for `inputMethodManager`. - inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - - // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. - supportAppBar = (Toolbar) findViewById(R.id.app_bar); - setSupportActionBar(supportAppBar); - appBar = getSupportActionBar(); - - // This is needed to get rid of the Android Studio warning that `appBar` might be null. - assert appBar != null; - - // Add the custom url_app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar. - appBar.setCustomView(R.layout.url_app_bar); - appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); - - // Set the "go" button on the keyboard to load the URL in urlTextBox. - urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox); - urlTextBox.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - // If the event is a key-down event on the `enter` button, load the URL. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // Load the URL into the mainWebView and consume the event. - try { - loadUrlFromTextBox(); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - // If the enter key was pressed, consume the event. - return true; - } else { - // If any other key was pressed, do not consume the event. - return false; - } - } - }); - - // Set `waitingForOrbotHTMLString`. - waitingForOrbotHTMLString = "

" + getString(R.string.waiting_for_orbot) + "

"; - - // Initialize `pendingUrl`. - pendingUrl = ""; - - // Set the initial Orbot status. - orbotStatus = "unknown"; - - // Create an Orbot status `BroadcastReceiver`. - BroadcastReceiver orbotStatusBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // Store the content of the status message in `orbotStatus`. - orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS"); - - // If we are waiting on `pendingUrl`, load it now that Orbot is connected. - if (orbotStatus.equals("ON") && !pendingUrl.isEmpty()) { - - // Wait 500 milliseconds, because Orbot isn't really ready yet. - try { - Thread.sleep(500); - } catch (InterruptedException exception) { - // Do nothing. - } - - // Copy `pendingUrl` to `formattedUrlString` and reset `pendingUrl` to be empty. - formattedUrlString = pendingUrl; - pendingUrl = ""; - - // Load `formattedUrlString - mainWebView.loadUrl(formattedUrlString, customHeaders); - } - } - }; - - // Register `orbotStatusBroadcastReceiver` on `this` context. - this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS")); - - // Get handles for views that need to be accessed. - drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout); - rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout); - mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.main_webview_relativelayout); - mainWebView = (WebView) findViewById(R.id.mainWebView); - findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); - findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext); - fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.full_screen_video_framelayout); - - // Create a double-tap listener to toggle full-screen mode. - final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { - // Override `onDoubleTap()`. All other events are handled using the default settings. - @Override - public boolean onDoubleTap(MotionEvent event) { - if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled. - // Toggle `inFullScreenBrowsingMode`. - inFullScreenBrowsingMode = !inFullScreenBrowsingMode; - - if (inFullScreenBrowsingMode) { // Switch to full screen mode. - // Hide the `appBar`. - appBar.hide(); - - // Hide the `BannerAd` in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.hideAd(adView); - } - - // Modify the system bars. - if (hideSystemBarsOnFullscreen) { // Hide everything. - // Remove the translucent overlays. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. - drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - - /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. - * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. - * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown. - */ - rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - - // Set `rootCoordinatorLayout` to fill the whole screen. - rootCoordinatorLayout.setFitsSystemWindows(false); - } else { // Hide everything except the status and navigation bars. - // Set `rootCoordinatorLayout` to fit under the status and navigation bars. - rootCoordinatorLayout.setFitsSystemWindows(false); - - if (translucentNavigationBarOnFullscreen) { // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen. - // Set the navigation bar to be translucent. - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - } - } - } else { // Switch to normal viewing mode. - // Show the `appBar`. - appBar.show(); - - // Show the `BannerAd` in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. - BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); - - // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. - adView = findViewById(R.id.adView); - } - - // Remove the translucent navigation bar flag if it is set. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - - // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`. - rootCoordinatorLayout.setSystemUiVisibility(0); - - // Constrain `rootCoordinatorLayout` inside the status and navigation bars. - rootCoordinatorLayout.setFitsSystemWindows(true); - } - - // Consume the double-tap. - return true; - } else { // Do not consume the double-tap because full screen browsing mode is disabled. - return false; - } - } - }); - - // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps. - mainWebView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - // Send the `event` to `gestureDetector`. - return gestureDetector.onTouchEvent(event); - } - }); - - // Update `findOnPageCountTextView`. - mainWebView.setFindListener(new WebView.FindListener() { - // Get a handle for `findOnPageCountTextView`. - final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview); - - @Override - public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) { - if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches. - // Set `findOnPageCountTextView` to `0/0`. - findOnPageCountTextView.setText(R.string.zero_of_zero); - } else if (isDoneCounting) { // There are matches. - // `activeMatchOrdinal` is zero-based. - int activeMatch = activeMatchOrdinal + 1; - - // Set `findOnPageCountTextView`. - findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches); - } - } - }); - - // Search for the string on the page whenever a character changes in the `findOnPageEditText`. - findOnPageEditText.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // Do nothing. - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - // Do nothing. - } - - @Override - public void afterTextChanged(Editable s) { - // Search for the text in `mainWebView`. - mainWebView.findAllAsync(findOnPageEditText.getText().toString()); - } - }); - - // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard. - findOnPageEditText.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed. - // Hide the soft keyboard. `0` indicates no additional flags. - inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); - - // Consume the event. - return true; - } else { // A different key was pressed. - // Do not consume the event. - return false; - } - } - }); - - // Implement swipe to refresh - swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout); - swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); - swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - mainWebView.reload(); - } - }); - - // `DrawerTitle` identifies the `DrawerLayout` in accessibility mode. - drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer)); - - // Listen for touches on the navigation menu. - final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationview); - navigationView.setNavigationItemSelectedListener(this); - - // Get handles for `navigationMenu` and the back and forward menu items. The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu. - final Menu navigationMenu = navigationView.getMenu(); - final MenuItem navigationBackMenuItem = navigationMenu.getItem(1); - final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2); - final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3); - - // The `DrawerListener` allows us to update the Navigation Menu. - drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - } - - @Override - public void onDrawerOpened(View drawerView) { - } - - @Override - public void onDrawerClosed(View drawerView) { - } - - @Override - public void onDrawerStateChanged(int newState) { - // Update the `Back`, `Forward`, and `History` menu items every time the drawer opens. - navigationBackMenuItem.setEnabled(mainWebView.canGoBack()); - navigationForwardMenuItem.setEnabled(mainWebView.canGoForward()); - navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward())); - - // Hide the keyboard so we can see the navigation menu. `0` indicates no additional flags. - inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); - } - }); - - // drawerToggle creates the hamburger icon at the start of the AppBar. - drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer); - - // Initialize `adServerSet`. - final Set adServersSet = new HashSet<>(); - - // Load the list of ad servers into memory. - try { - // Load `pgl.yoyo.org_adservers.txt` into a `BufferedReader`. - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("pgl.yoyo.org_adservers.txt"))); - - // Create a string for storing each ad server. - String adServer; - - // Populate `adServersSet`. - while ((adServer = bufferedReader.readLine()) != null) { - adServersSet.add(adServer); - } - - // Close `bufferedReader`. - bufferedReader.close(); - } catch (IOException ioException) { - // We're pretty sure the asset exists, so we don't need to worry about the `IOException` ever being thrown. - } - - mainWebView.setWebViewClient(new WebViewClient() { - // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps. - // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24. - @SuppressWarnings("deprecation") - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - // Use an external email program if the link begins with `mailto:`. - if (url.startsWith("mailto:")) { - // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. - Intent emailIntent = new Intent(Intent.ACTION_SENDTO); - - // Parse the url and set it as the data for the `Intent`. - emailIntent.setData(Uri.parse(url)); - - // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. - emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Make it so. - startActivity(emailIntent); - return true; - } else { // Load the URL in Privacy Browser. - mainWebView.loadUrl(url, customHeaders); - return true; - } - } - - // Block ads. We have to use the deprecated `shouldInterceptRequest` until minimum API >= 21. - @SuppressWarnings("deprecation") - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url){ - if (adBlockerEnabled) { // Block ads. - // Extract the host from `url`. - Uri requestUri = Uri.parse(url); - String requestHost = requestUri.getHost(); - - // Create a variable to track if this is an ad server. - boolean requestHostIsAdServer = false; - - // Check all the subdomains of `requestHost` if it is not `null`. - if (requestHost != null) { - while (requestHost.contains(".")) { - if (adServersSet.contains(requestHost)) { - requestHostIsAdServer = true; - } - - // Strip out the lowest subdomain of `requestHost`. - requestHost = requestHost.substring(requestHost.indexOf(".") + 1); - } - } - - if (requestHostIsAdServer) { // It is an ad server. - // Return an empty `WebResourceResponse`. - return new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes())); - } else { // It is not an ad server. - // `return null` loads the requested resource. - return null; - } - } else { // Ad blocking is disabled. - // `return null` loads the requested resource. - return null; - } - } - - // Update the URL in urlTextBox when the page starts to load. - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - // Check to see if we are waiting on Orbot. - if (pendingUrl.isEmpty()) { // We are not waiting on Orbot, so we need to process the URL. - // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded. - formattedUrlString = url; - - // Display the loading URL is the URL text box. - urlTextBox.setText(url); - } - } - - // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load. - @Override - public void onPageFinished(WebView view, String url) { - // Check to see if we are waiting on Orbot. - if (pendingUrl.isEmpty()) { // we are not waiting on Orbot, so we need to process the URL. - formattedUrlString = url; - - // Only update urlTextBox if the user is not typing in it. - if (!urlTextBox.hasFocus()) { - urlTextBox.setText(formattedUrlString); - } - - // Store the SSL certificate so it can be accessed from `ViewSslCertificate`. - sslCertificate = mainWebView.getCertificate(); - } - } - - // Handle SSL Certificate errors. - @Override - public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`. - sslErrorHandler = handler; - - // Display the SSL error `AlertDialog`. - AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error); - sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); - } - }); - - mainWebView.setWebChromeClient(new WebChromeClient() { - // Update the progress bar when a page is loading. - @Override - public void onProgressChanged(WebView view, int progress) { - ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar); - progressBar.setProgress(progress); - if (progress < 100) { - progressBar.setVisibility(View.VISIBLE); - } else { - progressBar.setVisibility(View.GONE); - - //Stop the `SwipeToRefresh` indicator if it is running - swipeRefreshLayout.setRefreshing(false); - } - } - - // Set the favorite icon when it changes. - @Override - public void onReceivedIcon(WebView view, Bitmap icon) { - // Save a copy of the favorite icon for use if a shortcut is added to the home screen. - favoriteIcon = icon; - - // Place the favorite icon in the appBar. - ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon); - imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true)); - } - - // Enter full screen video - @Override - public void onShowCustomView(View view, CustomViewCallback callback) { - // Pause the ad if this is the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.pauseAd(adView); - } - - // Remove the translucent overlays. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. - drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - - /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. - * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. - * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown. - */ - rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - - // Set `rootCoordinatorLayout` to fill the entire screen. - rootCoordinatorLayout.setFitsSystemWindows(false); - - // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen. - fullScreenVideoFrameLayout.addView(view); - fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); - } - - // Exit full screen video - public void onHideCustomView() { - // Hide `fullScreenVideoFrameLayout`. - fullScreenVideoFrameLayout.removeAllViews(); - fullScreenVideoFrameLayout.setVisibility(View.GONE); - - // Add the translucent status flag. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Set `rootCoordinatorLayout` to fit inside the status and navigation bars. This also clears the `SYSTEM_UI` flags. - rootCoordinatorLayout.setFitsSystemWindows(true); - - // Show the ad if this is the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. - BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); - - // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. - adView = findViewById(R.id.adView); - } - } - }); - - // Register `mainWebView` for a context menu. This is used to see link targets and download images. - registerForContextMenu(mainWebView); - - // Allow the downloading of files. - mainWebView.setDownloadListener(new DownloadListener() { - @Override - public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { - // Show the `DownloadFile` `AlertDialog` and name this instance `@string/download`. - AppCompatDialogFragment downloadFileDialogFragment = DownloadFile.fromUrl(url, contentDisposition, contentLength); - downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); - } - }); - - // Allow pinch to zoom. - mainWebView.getSettings().setBuiltInZoomControls(true); - - // Hide zoom controls. - mainWebView.getSettings().setDisplayZoomControls(false); - - // Initialize cookieManager. - cookieManager = CookieManager.getInstance(); - - // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard). - customHeaders.put("X-Requested-With", ""); - - // Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default. - PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - - // Get the intent information that started the app. - final Intent intent = getIntent(); - - if (intent.getData() != null) { - // Get the intent data and convert it to a string. - final Uri intentUriData = intent.getData(); - formattedUrlString = intentUriData.toString(); - } - - // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode. - inFullScreenBrowsingMode = false; - - // Initialize AdView for the free flavor. - adView = findViewById(R.id.adView); - - // Apply the settings from the shared preferences. - applySettings(); - - // Load `formattedUrlString` if we are not proxying through Orbot and waiting for Orbot to connect. - if (!(proxyThroughOrbot && !orbotStatus.equals("ON"))) { - mainWebView.loadUrl(formattedUrlString, customHeaders); - } - - // If the favorite icon is null, load the default. - if (favoriteIcon == null) { - // We have to use `ContextCompat` until API >= 21. - Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world); - BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable; - favoriteIcon = favoriteIconBitmapDrawable.getBitmap(); - } - } - - - @Override - protected void onNewIntent(Intent intent) { - // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity. - setIntent(intent); - - if (intent.getData() != null) { - // Get the intent data and convert it to a string. - final Uri intentUriData = intent.getData(); - formattedUrlString = intentUriData.toString(); - } - - // Close the navigation drawer if it is open. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); - } - - // Load the website. - mainWebView.loadUrl(formattedUrlString, customHeaders); - - // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. - mainWebView.requestFocus(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.webview_options_menu, menu); - - // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`. - mainMenu = menu; - - // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(false); - - // Get handles for the menu items. - MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies); - MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies); - MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage); - MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData); - - // Only display third-Party Cookies if SDK >= 21 - toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21); - - // Get the shared preference values. `this` references the current context. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Set the status of the additional app bar icons. The default is `false`. - if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) { - toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); - } else { //Do not display the additional icons. - toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - } - - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - // Get handles for the menu items. - MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies); - MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies); - MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage); - MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData); - MenuItem clearCookies = menu.findItem(R.id.clearCookies); - MenuItem clearFormData = menu.findItem(R.id.clearFormData); - MenuItem refreshMenuItem = menu.findItem(R.id.refresh); - - // Set the status of the menu item checkboxes. - toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled); - toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled); - toggleDomStorage.setChecked(domStorageEnabled); - toggleSaveFormData.setChecked(saveFormDataEnabled); - - // Enable third-party cookies if first-party cookies are enabled. - toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled); - - // Enable DOM Storage if JavaScript is enabled. - toggleDomStorage.setEnabled(javaScriptEnabled); - - // Enable Clear Cookies if there are any. - clearCookies.setEnabled(cookieManager.hasCookies()); - - // Enable Clear Form Data is there is any. - WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); - clearFormData.setEnabled(mainWebViewDatabase.hasFormData()); - - // Only show `Refresh` if `swipeToRefresh` is disabled. - refreshMenuItem.setVisible(!swipeToRefreshEnabled); - - // Initialize font size variables. - int fontSize = mainWebView.getSettings().getTextZoom(); - String fontSizeTitle; - MenuItem selectedFontSizeMenuItem; - - // Prepare the font size title and current size menu item. - switch (fontSize) { - case 50: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent); - break; - - case 75: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent); - break; - - case 100: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); - break; - - case 125: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent); - break; - - case 150: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent); - break; - - case 175: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent); - break; - - case 200: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent); - break; - - default: - fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); - selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); - break; - } - - // Set the font size title and select the current size menu item. - MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize); - fontSizeMenuItem.setTitle(fontSizeTitle); - selectedFontSizeMenuItem.setChecked(true); - - // Run all the other default commands. - super.onPrepareOptionsMenu(menu); - - // `return true` displays the menu. - return true; - } - - @Override - // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. - @SuppressLint("SetJavaScriptEnabled") - // removeAllCookies is deprecated, but it is required for API < 21. - @SuppressWarnings("deprecation") - public boolean onOptionsItemSelected(MenuItem menuItem) { - int menuItemId = menuItem.getItemId(); - - // Set the commands that relate to the menu entries. - switch (menuItemId) { - case R.id.toggleJavaScript: - // Switch the status of javaScriptEnabled. - javaScriptEnabled = !javaScriptEnabled; - - // Apply the new JavaScript status. - mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Display a `Snackbar`. - if (javaScriptEnabled) { // JavaScrip is enabled. - Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled. - Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); - } else { // Privacy mode. - Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the WebView. - mainWebView.reload(); - return true; - - case R.id.toggleFirstPartyCookies: - // Switch the status of firstPartyCookiesEnabled. - firstPartyCookiesEnabled = !firstPartyCookiesEnabled; - - // Update the menu checkbox. - menuItem.setChecked(firstPartyCookiesEnabled); - - // Apply the new cookie status. - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Display a `Snackbar`. - if (firstPartyCookiesEnabled) { // First-party cookies are enabled. - Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else if (javaScriptEnabled){ // JavaScript is still enabled. - Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); - } else { // Privacy mode. - Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the WebView. - mainWebView.reload(); - return true; - - case R.id.toggleThirdPartyCookies: - if (Build.VERSION.SDK_INT >= 21) { - // Switch the status of thirdPartyCookiesEnabled. - thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled; - - // Update the menu checkbox. - menuItem.setChecked(thirdPartyCookiesEnabled); - - // Apply the new cookie status. - cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); - - // Display a `Snackbar`. - if (thirdPartyCookiesEnabled) { - Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the WebView. - mainWebView.reload(); - } // Else do nothing because SDK < 21. - return true; - - case R.id.toggleDomStorage: - // Switch the status of domStorageEnabled. - domStorageEnabled = !domStorageEnabled; - - // Update the menu checkbox. - menuItem.setChecked(domStorageEnabled); - - // Apply the new DOM Storage status. - mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Display a `Snackbar`. - if (domStorageEnabled) { - Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show(); - } - - // Reload the WebView. - mainWebView.reload(); - return true; - - case R.id.toggleSaveFormData: - // Switch the status of saveFormDataEnabled. - saveFormDataEnabled = !saveFormDataEnabled; - - // Update the menu checkbox. - menuItem.setChecked(saveFormDataEnabled); - - // Apply the new form data status. - mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); - - // Display a `Snackbar`. - if (saveFormDataEnabled) { - Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show(); - } - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - // Reload the WebView. - mainWebView.reload(); - return true; - - case R.id.clearCookies: - if (Build.VERSION.SDK_INT < 21) { - cookieManager.removeAllCookie(); - } else { - cookieManager.removeAllCookies(null); - } - Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show(); - return true; - - case R.id.clearDomStorage: - WebStorage webStorage = WebStorage.getInstance(); - webStorage.deleteAllData(); - Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show(); - return true; - - case R.id.clearFormData: - WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); - mainWebViewDatabase.clearFormData(); - Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_deleted, Snackbar.LENGTH_SHORT).show(); - return true; - - case R.id.fontSizeFiftyPercent: - mainWebView.getSettings().setTextZoom(50); - return true; - - case R.id.fontSizeSeventyFivePercent: - mainWebView.getSettings().setTextZoom(75); - return true; - - case R.id.fontSizeOneHundredPercent: - mainWebView.getSettings().setTextZoom(100); - return true; - - case R.id.fontSizeOneHundredTwentyFivePercent: - mainWebView.getSettings().setTextZoom(125); - return true; - - case R.id.fontSizeOneHundredFiftyPercent: - mainWebView.getSettings().setTextZoom(150); - return true; - - case R.id.fontSizeOneHundredSeventyFivePercent: - mainWebView.getSettings().setTextZoom(175); - return true; - - case R.id.fontSizeTwoHundredPercent: - mainWebView.getSettings().setTextZoom(200); - return true; - - case R.id.find_on_page: - // Hide the URL app bar. - supportAppBar.setVisibility(View.GONE); - - // Show the Find on Page `RelativeLayout`. - findOnPageLinearLayout.setVisibility(View.VISIBLE); - - // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android. - // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working - findOnPageEditText.postDelayed(new Runnable() - { - @Override - public void run() - { - // Set the focus on `findOnPageEditText`. - findOnPageEditText.requestFocus(); - - // Display the keyboard. - inputMethodManager.showSoftInput(findOnPageEditText, 0); - } - }, 200); - return true; - - case R.id.share: - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString()); - shareIntent.setType("text/plain"); - startActivity(Intent.createChooser(shareIntent, "Share URL")); - return true; - - case R.id.addToHomescreen: - // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `R.string.create_shortcut`. - AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut(); - createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut)); - - //Everything else will be handled by `CreateHomeScreenShortcut` and the associated listener below. - return true; - - case R.id.print: - // Get a `PrintManager` instance. - PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); - - // Convert `mainWebView` to `printDocumentAdapter`. - PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter(); - - // Print the document. The print attributes are `null`. - printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); - return true; - - case R.id.refresh: - mainWebView.reload(); - return true; - - default: - // Don't consume the event. - return super.onOptionsItemSelected(menuItem); - } - } - - // removeAllCookies is deprecated, but it is required for API < 21. - @SuppressWarnings("deprecation") - @Override - public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { - int menuItemId = menuItem.getItemId(); - - switch (menuItemId) { - case R.id.home: - mainWebView.loadUrl(homepage, customHeaders); - break; - - case R.id.back: - if (mainWebView.canGoBack()) { - mainWebView.goBack(); - } - break; - - case R.id.forward: - if (mainWebView.canGoForward()) { - mainWebView.goForward(); - } - break; - - case R.id.history: - // Gte the `WebBackForwardList`. - WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList(); - - // Show the `UrlHistory` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`. - AppCompatDialogFragment urlHistoryDialogFragment = UrlHistory.loadBackForwardList(this, webBackForwardList); - urlHistoryDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.history)); - break; - - case R.id.bookmarks: - // Launch Bookmarks. - Intent bookmarksIntent = new Intent(this, Bookmarks.class); - startActivity(bookmarksIntent); - break; - - case R.id.downloads: - // Launch the system Download Manager. - Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS); - - // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list. - downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - startActivity(downloadManagerIntent); - break; - - case R.id.settings: - // Launch `Settings`. - Intent settingsIntent = new Intent(this, Settings.class); - startActivity(settingsIntent); - break; - - case R.id.domains: - // Launch `DomainsList`. - Intent domainsIntent = new Intent(this, DomainsList.class); - startActivity(domainsIntent); - break; - - case R.id.guide: - // Launch `Guide`. - Intent guideIntent = new Intent(this, Guide.class); - startActivity(guideIntent); - break; - - case R.id.about: - // Launch `About`. - Intent aboutIntent = new Intent(this, About.class); - startActivity(aboutIntent); - break; - - case R.id.clearAndExit: - // Clear cookies. The commands changed slightly in API 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.removeAllCookies(null); - } else { - cookieManager.removeAllCookie(); - } - - // Clear DOM storage. - WebStorage domStorage = WebStorage.getInstance(); - domStorage.deleteAllData(); - - // Clear form data. - WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); - webViewDatabase.clearFormData(); - - // Clear cache. The argument of "true" includes disk files. - mainWebView.clearCache(true); - - // Clear the back/forward history. - mainWebView.clearHistory(); - - // Clear any SSL certificate preferences. - mainWebView.clearSslPreferences(); - - // Clear `formattedUrlString`. - formattedUrlString = null; - - // Clear `customHeaders`. - customHeaders.clear(); - - // Detach all views from `mainWebViewRelativeLayout`. - mainWebViewRelativeLayout.removeAllViews(); - - // Destroy the internal state of `mainWebView`. - mainWebView.destroy(); - - // Manually delete the `app_webview` folder, which contains an additional `WebView` cache. See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. - Runtime runtime = Runtime.getRuntime(); - String dataDirString = getApplicationInfo().dataDir; // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`. - try { - runtime.exec("rm -rf " + dataDirString + "/app_webview"); - } catch (IOException e) { - // Do nothing if the files do not exist. - } - - // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. - if (Build.VERSION.SDK_INT >= 21) { - finishAndRemoveTask(); - } else { - finish(); - } - - // Remove the terminated program from RAM. The status code is `0`. - System.exit(0); - break; - - default: - break; - } - - // Close the navigation drawer. - drawerLayout.closeDrawer(GravityCompat.START); - return true; - } - - @Override - public void onPostCreate(Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - - // Sync the state of the DrawerToggle after onRestoreInstanceState has finished. - drawerToggle.syncState(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - - // Reload the ad for the free flavor if we are not in full screen mode. - if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) { - // Reload the ad. - BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); - - // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. - adView = findViewById(R.id.adView); - } - - // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug: https://code.google.com/p/android/issues/detail?id=20493#c8 - // ActivityCompat.invalidateOptionsMenu(this); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { - // Store the `HitTestResult`. - final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult(); - - // Create strings. - final String imageUrl; - final String linkUrl; - - // Get a handle for the `ClipboardManager`. - final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - - switch (hitTestResult.getType()) { - // `SRC_ANCHOR_TYPE` is a link. - case WebView.HitTestResult.SRC_ANCHOR_TYPE: - // Get the target URL. - linkUrl = hitTestResult.getExtra(); - - // Set the target URL as the title of the `ContextMenu`. - menu.setHeaderTitle(linkUrl); - - // Add a `Load URL` entry. - menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - mainWebView.loadUrl(linkUrl, customHeaders); - return false; - } - }); - - // Add a `Copy URL` entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // Save the link URL in a `ClipData`. - ClipData srcAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), linkUrl); - - // Set the `ClipData` as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcAnchorTypeClipData); - return false; - } - }); - - // Add a `Cancel` entry, which by default closes the `ContextMenu`. - menu.add(R.string.cancel); - break; - - case WebView.HitTestResult.EMAIL_TYPE: - // Get the target URL. - linkUrl = hitTestResult.getExtra(); - - // Set the target URL as the title of the `ContextMenu`. - menu.setHeaderTitle(linkUrl); - - // Add a `Write Email` entry. - menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. - Intent emailIntent = new Intent(Intent.ACTION_SENDTO); - - // Parse the url and set it as the data for the `Intent`. - emailIntent.setData(Uri.parse("mailto:" + linkUrl)); - - // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. - emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - // Make it so. - startActivity(emailIntent); - return false; - } - }); - - // Add a `Copy Email Address` entry. - menu.add(R.string.copy_email_address).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // Save the email address in a `ClipData`. - ClipData srcEmailTypeClipData = ClipData.newPlainText(getResources().getString(R.string.email_address), linkUrl); - - // Set the `ClipData` as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcEmailTypeClipData); - return false; - } - }); - - // Add a `Cancel` entry, which by default closes the `ContextMenu`. - menu.add(R.string.cancel); - break; - - // `IMAGE_TYPE` is an image. - case WebView.HitTestResult.IMAGE_TYPE: - // Get the image URL. - imageUrl = hitTestResult.getExtra(); - - // Set the image URL as the title of the `ContextMenu`. - menu.setHeaderTitle(imageUrl); - - // Add a `View Image` entry. - menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - mainWebView.loadUrl(imageUrl, customHeaders); - return false; - } - }); - - // Add a `Download Image` entry. - menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`. - AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl); - downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); - return false; - } - }); - - // Add a `Copy URL` entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // Save the image URL in a `ClipData`. - ClipData srcImageTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl); - - // Set the `ClipData` as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcImageTypeClipData); - return false; - } - }); - - // Add a `Cancel` entry, which by default closes the `ContextMenu`. - menu.add(R.string.cancel); - break; - - - // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. - case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: - // Get the image URL. - imageUrl = hitTestResult.getExtra(); - - // Set the image URL as the title of the `ContextMenu`. - menu.setHeaderTitle(imageUrl); - - // Add a `View Image` entry. - menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - mainWebView.loadUrl(imageUrl, customHeaders); - return false; - } - }); - - // Add a `Download Image` entry. - menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`. - AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl); - downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); - return false; - } - }); - - // Add a `Copy URL` entry. - menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - // Save the image URL in a `ClipData`. - ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl); - - // Set the `ClipData` as the clipboard's primary clip. - clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData); - return false; - } - }); - - // Add a `Cancel` entry, which by default closes the `ContextMenu`. - menu.add(R.string.cancel); - break; - } - } - - @Override - public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) { - // Get shortcutNameEditText from the alert dialog. - EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext); - - // Create the bookmark shortcut based on formattedUrlString. - Intent bookmarkShortcut = new Intent(); - bookmarkShortcut.setAction(Intent.ACTION_VIEW); - bookmarkShortcut.setData(Uri.parse(formattedUrlString)); - - // Place the bookmark shortcut on the home screen. - Intent placeBookmarkShortcut = new Intent(); - placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut); - placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString()); - placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon); - placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - sendBroadcast(placeBookmarkShortcut); - } - - @Override - public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) { - // Download the image if it has an HTTP or HTTPS URI. - if (imageUrl.startsWith("http")) { - // Get a handle for the system `DOWNLOAD_SERVICE`. - DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); - - // Parse `imageUrl`. - DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl)); - - // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login. - if (firstPartyCookiesEnabled) { - // Get the cookies for `imageUrl`. - String cookies = cookieManager.getCookie(imageUrl); - - // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`. - downloadRequest.addRequestHeader("Cookie", cookies); - } - - // Get the file name from `dialogFragment`. - EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name); - String imageName = downloadImageNameEditText.getText().toString(); - - // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`. - if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`. - downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName); - } else { // Only set the title using `imageName`. - downloadRequest.setTitle(imageName); - } - - // Allow `MediaScanner` to index the download if it is a media file. - downloadRequest.allowScanningByMediaScanner(); - - // Add the URL as the description for the download. - downloadRequest.setDescription(imageUrl); - - // Show the download notification after the download is completed. - downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - - // Initiate the download. - downloadManager.enqueue(downloadRequest); - } else { // The image is not an HTTP or HTTPS URI. - Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show(); - } - } - - @Override - public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) { - // Download the file if it has an HTTP or HTTPS URI. - if (downloadUrl.startsWith("http")) { - - // Get a handle for the system `DOWNLOAD_SERVICE`. - DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); - - // Parse `downloadUrl`. - DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl)); - - // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login. - if (firstPartyCookiesEnabled) { - // Get the cookies for `downloadUrl`. - String cookies = cookieManager.getCookie(downloadUrl); - - // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`. - downloadRequest.addRequestHeader("Cookie", cookies); - } - - // Get the file name from `dialogFragment`. - EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name); - String fileName = downloadFileNameEditText.getText().toString(); - - // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`. - if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download location to `/sdcard/Android/data/com.stoutner.privacybrowser.standard/files` named `fileName`. - downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName); - } else { // Only set the title using `fileName`. - downloadRequest.setTitle(fileName); - } - - // Allow `MediaScanner` to index the download if it is a media file. - downloadRequest.allowScanningByMediaScanner(); - - // Add the URL as the description for the download. - downloadRequest.setDescription(downloadUrl); - - // Show the download notification after the download is completed. - downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - - // Initiate the download. - downloadManager.enqueue(downloadRequest); - } else { // The download is not an HTTP or HTTPS URI. - Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show(); - } - } - - public void viewSslCertificate(View view) { - // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`. - DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate(); - viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate)); - } - - @Override - public void onSslErrorCancel() { - sslErrorHandler.cancel(); - } - - @Override - public void onSslErrorProceed() { - sslErrorHandler.proceed(); - } - - @Override - public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) { - // Load the history entry. - mainWebView.goBackOrForward(moveBackOrForwardSteps); - } - - @Override - public void onClearHistory() { - // Clear the history. - mainWebView.clearHistory(); - } - - // Override `onBackPressed` to handle the navigation drawer and `mainWebView`. - @Override - public void onBackPressed() { - // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text. - if (drawerLayout.isDrawerVisible(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); - } else { - // Load the previous URL if available. - if (mainWebView.canGoBack()) { - mainWebView.goBack(); - } else { - // Pass `onBackPressed()` to the system. - super.onBackPressed(); - } - } - } - - @Override - public void onPause() { - // Pause `mainWebView`. - mainWebView.onPause(); - - // Stop all JavaScript. - mainWebView.pauseTimers(); - - // Pause the adView or it will continue to consume resources in the background on the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.pauseAd(adView); - } - - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - - // Resume JavaScript (if enabled). - mainWebView.resumeTimers(); - - // Resume `mainWebView`. - mainWebView.onResume(); - - // Resume the adView for the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - BannerAd.resumeAd(adView); - } - } - - @Override - public void onRestart() { - super.onRestart(); - - // Apply the settings from shared preferences, which might have been changed in `Settings`. - applySettings(); - - // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. - updatePrivacyIcons(true); - - } - - private void loadUrlFromTextBox() throws UnsupportedEncodingException { - // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. - String unformattedUrlString = urlTextBox.getText().toString().trim(); - - URL unformattedUrl = null; - Uri.Builder formattedUri = new Uri.Builder(); - - // Check to see if unformattedUrlString is a valid URL. Otherwise, convert it into a Duck Duck Go search. - if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) { - // Add http:// at the beginning if it is missing. Otherwise the app will segfault. - if (!unformattedUrlString.startsWith("http")) { - unformattedUrlString = "http://" + unformattedUrlString; - } - - // Convert unformattedUrlString to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components. - try { - unformattedUrl = new URL(unformattedUrlString); - } catch (MalformedURLException e) { - e.printStackTrace(); - } - - // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value. - final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null; - final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null; - final String path = unformattedUrl != null ? unformattedUrl.getPath() : null; - final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null; - final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null; - - formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment); - formattedUrlString = formattedUri.build().toString(); - } else { - // Sanitize the search input and convert it to a DuckDuckGo search. - final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8"); - - // Use the correct search URL. - if (javaScriptEnabled) { // JavaScript is enabled. - formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString; - } else { // JavaScript is disabled. - formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString; - } - } - - mainWebView.loadUrl(formattedUrlString, customHeaders); - - // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. - inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); - } - - public void findPreviousOnPage(View view) { - // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. - mainWebView.findNext(false); - } - - public void findNextOnPage(View view) { - // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. - mainWebView.findNext(true); - } - - public void closeFindOnPage(View view) { - // Delete the contents of `find_on_page_edittext`. - findOnPageEditText.setText(null); - - // Clear the highlighted phrases. - mainWebView.clearMatches(); - - // Hide the Find on Page `RelativeLayout`. - findOnPageLinearLayout.setVisibility(View.GONE); - - // Show the URL app bar. - supportAppBar.setVisibility(View.VISIBLE); - - // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. - inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); - } - - private void applySettings() { - // Get the shared preference values. `this` references the current context. - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); - - // Store the values from `sharedPreferences` in variables. - String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); - String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); - String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); - String javaScriptDisabledSearchCustomURLString = sharedPreferences.getString("javascript_disabled_search_custom_url", ""); - String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); - String javaScriptEnabledSearchCustomURLString = sharedPreferences.getString("javascript_enabled_search_custom_url", ""); - String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com"); - String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion"); - String torJavaScriptDisabledSearchString = sharedPreferences.getString("tor_javascript_disabled_search", "https://3g2upl4pq6kufc4m.onion/html/?q="); - String torJavaScriptDisabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_disabled_search_custom_url", ""); - String torJavaScriptEnabledSearchString = sharedPreferences.getString("tor_javascript_enabled_search", "https://3g2upl4pq6kufc4m.onion/?q="); - String torJavaScriptEnabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_enabled_search_custom_url", ""); - String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); - swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false); - adBlockerEnabled = sharedPreferences.getBoolean("block_ads", true); - boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false); - proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); - fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("enable_full_screen_browsing_mode", false); - hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false); - translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true); - - // Because they can be modified on-the-fly by the user, these default settings are only applied when the program first runs. - if (javaScriptEnabled == null) { // If `javaScriptEnabled` is null the program is just starting. - // Get the values from `sharedPreferences`. - javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false); - firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false); - thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false); - domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); - saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); - - // Apply the default settings. - mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); - cookieManager.setAcceptCookie(firstPartyCookiesEnabled); - mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); - mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); - mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); - - // Set third-party cookies status if API >= 21. - if (Build.VERSION.SDK_INT >= 21) { - cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); - } - } - - // Set the homepage, search, and proxy options. - if (proxyThroughOrbot) { // Set the Tor options. - // Set `torHomepageString` as `homepage`. - homepage = torHomepageString; - - // If formattedUrlString is null assign the homepage to it. - if (formattedUrlString == null) { - formattedUrlString = homepage; - } - - // Set JavaScript disabled search. - if (torJavaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string. - javaScriptDisabledSearchURL = torJavaScriptDisabledSearchCustomURLString; - } else { // Use the string from the pre-built list. - javaScriptDisabledSearchURL = torJavaScriptDisabledSearchString; - } - - // Set JavaScript enabled search. - if (torJavaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string. - javaScriptEnabledSearchURL = torJavaScriptEnabledSearchCustomURLString; - } else { // Use the string from the pre-built list. - javaScriptEnabledSearchURL = torJavaScriptEnabledSearchString; - } - - // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed. - OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118"); - - // Display a message to the user if we are waiting on Orbot. - if (!orbotStatus.equals("ON")) { - // Save `formattedUrlString` in `pendingUrl`. - pendingUrl = formattedUrlString; - - // Load a waiting page. `null` specifies no encoding, which defaults to ASCII. - mainWebView.loadData(waitingForOrbotHTMLString, "text/html", null); - } - } else { // Set the non-Tor options. - // Set `homepageString` as `homepage`. - homepage = homepageString; - - // If formattedUrlString is null assign the homepage to it. - if (formattedUrlString == null) { - formattedUrlString = homepage; - } - - // Set JavaScript disabled search. - if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string. - javaScriptDisabledSearchURL = javaScriptDisabledSearchCustomURLString; - } else { // Use the string from the pre-built list. - javaScriptDisabledSearchURL = javaScriptDisabledSearchString; - } - - // Set JavaScript enabled search. - if (javaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string. - javaScriptEnabledSearchURL = javaScriptEnabledSearchCustomURLString; - } else { // Use the string from the pre-built list. - javaScriptEnabledSearchURL = javaScriptEnabledSearchString; - } - - // Reset the proxy to default. The host is `""` and the port is `"0"`. - OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0"); - - // Reset `pendingUrl` if we are currently waiting for Orbot to connect. - if (!pendingUrl.isEmpty()) { - formattedUrlString = pendingUrl; - pendingUrl = ""; - } - } - - // Set swipe to refresh. - swipeRefreshLayout.setEnabled(swipeToRefreshEnabled); - - // Set the user agent initial status. - switch (userAgentString) { - case "Default user agent": - // Set the user agent to `""`, which uses the default value. - mainWebView.getSettings().setUserAgentString(""); - break; - - case "Custom user agent": - // Set the custom user agent. - mainWebView.getSettings().setUserAgentString(customUserAgentString); - break; - - default: - // Use the selected user agent. - mainWebView.getSettings().setUserAgentString(userAgentString); - break; - } - - // Set Do Not Track status. - if (doNotTrackEnabled) { - customHeaders.put("DNT", "1"); - } else { - customHeaders.remove("DNT"); - } - - // Apply the appropriate full screen mode the `SYSTEM_UI` flags. - if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { - if (hideSystemBarsOnFullscreen) { // Hide everything. - // Remove the translucent navigation setting if it is currently flagged. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - - // Remove the translucent status bar overlay. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. - drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - - /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. - * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. - * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown. - */ - rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } else { // Hide everything except the status and navigation bars. - // Add the translucent status flag if it is unset. - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - if (translucentNavigationBarOnFullscreen) { - // Set the navigation bar to be translucent. - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - } else { - // Set the navigation bar to be black. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - } - } - } else { // Switch to normal viewing mode. - // Reset `inFullScreenBrowsingMode` to `false`. - inFullScreenBrowsingMode = false; - - // Show the `appBar`. - appBar.show(); - - // Show the `BannerAd` in the free flavor. - if (BuildConfig.FLAVOR.contentEquals("free")) { - // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. - BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); - - // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. - adView = findViewById(R.id.adView); - } - - // Remove the translucent navigation bar flag if it is set. - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); - - // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - - // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`. - rootCoordinatorLayout.setSystemUiVisibility(0); - - // Constrain `rootCoordinatorLayout` inside the status and navigation bars. - rootCoordinatorLayout.setFitsSystemWindows(true); - } - } - - private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) { - // Get handles for the icons. - MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript); - MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies); - MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage); - MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData); - - // Update `privacyIcon`. - if (javaScriptEnabled) { // JavaScript is enabled. - privacyIcon.setIcon(R.drawable.javascript_enabled); - } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled. - privacyIcon.setIcon(R.drawable.warning); - } else { // All the dangerous features are disabled. - privacyIcon.setIcon(R.drawable.privacy_mode); - } - - // Update `firstPartyCookiesIcon`. - if (firstPartyCookiesEnabled) { // First-party cookies are enabled. - firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled); - } else { // First-party cookies are disabled. - firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled); - } - - // Update `domStorageIcon`. - if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled. - domStorageIcon.setIcon(R.drawable.dom_storage_enabled); - } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled. - domStorageIcon.setIcon(R.drawable.dom_storage_disabled); - } else { // JavaScript is disabled, so DOM storage is ghosted. - domStorageIcon.setIcon(R.drawable.dom_storage_ghosted); - } - - // Update `formDataIcon`. - if (saveFormDataEnabled) { // Form data is enabled. - formDataIcon.setIcon(R.drawable.form_data_enabled); - } else { // Form data is disabled. - formDataIcon.setIcon(R.drawable.form_data_disabled); - } - - // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity. - if (runInvalidateOptionsMenu) { - ActivityCompat.invalidateOptionsMenu(this); - } - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java new file mode 100644 index 00000000..2cad4b6d --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -0,0 +1,2055 @@ +/* + * Copyright 2015-2017 Soren Stoutner . + * + * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.annotation.SuppressLint; +import android.app.DialogFragment; +import android.app.DownloadManager; +import android.content.BroadcastReceiver; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.net.http.SslCertificate; +import android.net.http.SslError; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.print.PrintDocumentAdapter; +import android.print.PrintManager; +import android.support.annotation.NonNull; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.NavigationView; +import android.support.design.widget.Snackbar; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +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.text.Editable; +import android.text.TextWatcher; +import android.util.Patterns; +import android.view.ContextMenu; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.webkit.CookieManager; +import android.webkit.DownloadListener; +import android.webkit.SslErrorHandler; +import android.webkit.WebBackForwardList; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceResponse; +import android.webkit.WebStorage; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.webkit.WebViewDatabase; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.stoutner.privacybrowser.BannerAd; +import com.stoutner.privacybrowser.BuildConfig; +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog; +import com.stoutner.privacybrowser.dialogs.DownloadImageDialog; +import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog; +import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog; +import com.stoutner.privacybrowser.helpers.OrbotProxyHelper; +import com.stoutner.privacybrowser.dialogs.DownloadFileDialog; +import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +// 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, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, + SslCertificateErrorDialog.SslCertificateErrorListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, UrlHistoryDialog.UrlHistoryListener { + + // `appBar` is public static so it can be accessed from `OrbotProxyHelper`. + // It is also used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, and `applySettings()`. + public static ActionBar appBar; + + // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, and `EditBookmarkDialog`. + // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`. + public static Bitmap favoriteIcon; + + // `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; + + // `sslCertificate` is public static so it can be accessed from `ViewSslCertificateDialog`. It is also used in `onCreate()`. + public static SslCertificate sslCertificate; + + // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`. + public static String orbotStatus; + + + // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`. + private DrawerLayout drawerLayout; + + // `rootCoordinatorLayout` is used in `onCreate()` and `applySettings()`. + private CoordinatorLayout rootCoordinatorLayout; + + // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`. + private WebView mainWebView; + + // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`. + private FrameLayout fullScreenVideoFrameLayout; + + // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`. + private SwipeRefreshLayout swipeRefreshLayout; + + // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `onRestart()`. + private CookieManager cookieManager; + + // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrlFromTextBox()`. + private final Map customHeaders = new HashMap<>(); + + // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`. + // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`. + private Boolean javaScriptEnabled; + + // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applySettings()`. + private boolean firstPartyCookiesEnabled; + + // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`. + private boolean thirdPartyCookiesEnabled; + + // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`. + private boolean domStorageEnabled; + + // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`. + private boolean saveFormDataEnabled; + + // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`. + private boolean swipeToRefreshEnabled; + + // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`. + private String homepage; + + // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`. + private String javaScriptDisabledSearchURL; + + // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`. + private String javaScriptEnabledSearchURL; + + // `adBlockerEnabled` is used in `onCreate()` and `applySettings()`. + private boolean adBlockerEnabled; + + // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applySettings()`. + private boolean fullScreenBrowsingModeEnabled; + + // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applySettings()`. + private boolean inFullScreenBrowsingMode; + + // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applySettings()`. + private boolean hideSystemBarsOnFullscreen; + + // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applySettings()`. + private boolean translucentNavigationBarOnFullscreen; + + // `proxyThroughOrbot` is used in `onCreate()` and `applySettings()` + private boolean proxyThroughOrbot; + + // `pendingUrl` is used in `onCreate()` and `applySettings()` + private static String pendingUrl; + + // `waitingForOrbotData` is used in `onCreate()` and `applySettings()`. + private String waitingForOrbotHTMLString; + + // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. + private LinearLayout findOnPageLinearLayout; + + // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. + private EditText findOnPageEditText; + + // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`. + private Menu mainMenu; + + // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`. + private ActionBarDrawerToggle drawerToggle; + + // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. + private Toolbar supportAppBar; + + // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`. + private EditText urlTextBox; + + // `adView` is used in `onCreate()` and `onConfigurationChanged()`. + private View adView; + + // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`. + private SslErrorHandler sslErrorHandler; + + // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`. + private InputMethodManager inputMethodManager; + + // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`. + private RelativeLayout mainWebViewRelativeLayout; + + @Override + // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers. + @SuppressLint("SetJavaScriptEnabled") + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.drawerlayout); + + // Get a handle for `inputMethodManager`. + inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21. + supportAppBar = (Toolbar) findViewById(R.id.app_bar); + setSupportActionBar(supportAppBar); + appBar = getSupportActionBar(); + + // This is needed to get rid of the Android Studio warning that `appBar` might be null. + assert appBar != null; + + // Add the custom url_app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar. + appBar.setCustomView(R.layout.url_app_bar); + appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); + + // Set the "go" button on the keyboard to load the URL in urlTextBox. + urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox); + urlTextBox.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + // If the event is a key-down event on the `enter` button, load the URL. + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // Load the URL into the mainWebView and consume the event. + try { + loadUrlFromTextBox(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + // If the enter key was pressed, consume the event. + return true; + } else { + // If any other key was pressed, do not consume the event. + return false; + } + } + }); + + // Set `waitingForOrbotHTMLString`. + waitingForOrbotHTMLString = "

" + getString(R.string.waiting_for_orbot) + "

"; + + // Initialize `pendingUrl`. + pendingUrl = ""; + + // Set the initial Orbot status. + orbotStatus = "unknown"; + + // Create an Orbot status `BroadcastReceiver`. + BroadcastReceiver orbotStatusBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // Store the content of the status message in `orbotStatus`. + orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS"); + + // If we are waiting on `pendingUrl`, load it now that Orbot is connected. + if (orbotStatus.equals("ON") && !pendingUrl.isEmpty()) { + + // Wait 500 milliseconds, because Orbot isn't really ready yet. + try { + Thread.sleep(500); + } catch (InterruptedException exception) { + // Do nothing. + } + + // Copy `pendingUrl` to `formattedUrlString` and reset `pendingUrl` to be empty. + formattedUrlString = pendingUrl; + pendingUrl = ""; + + // Load `formattedUrlString + mainWebView.loadUrl(formattedUrlString, customHeaders); + } + } + }; + + // Register `orbotStatusBroadcastReceiver` on `this` context. + this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS")); + + // Get handles for views that need to be accessed. + drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout); + rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout); + mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.main_webview_relativelayout); + mainWebView = (WebView) findViewById(R.id.mainWebView); + findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); + findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext); + fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.full_screen_video_framelayout); + + // Create a double-tap listener to toggle full-screen mode. + final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() { + // Override `onDoubleTap()`. All other events are handled using the default settings. + @Override + public boolean onDoubleTap(MotionEvent event) { + if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled. + // Toggle `inFullScreenBrowsingMode`. + inFullScreenBrowsingMode = !inFullScreenBrowsingMode; + + if (inFullScreenBrowsingMode) { // Switch to full screen mode. + // Hide the `appBar`. + appBar.hide(); + + // Hide the `BannerAd` in the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + BannerAd.hideAd(adView); + } + + // Modify the system bars. + if (hideSystemBarsOnFullscreen) { // Hide everything. + // Remove the translucent overlays. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. + drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + + /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. + * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. + * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown. + */ + rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + // Set `rootCoordinatorLayout` to fill the whole screen. + rootCoordinatorLayout.setFitsSystemWindows(false); + } else { // Hide everything except the status and navigation bars. + // Set `rootCoordinatorLayout` to fit under the status and navigation bars. + rootCoordinatorLayout.setFitsSystemWindows(false); + + if (translucentNavigationBarOnFullscreen) { // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen. + // Set the navigation bar to be translucent. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } + } + } else { // Switch to normal viewing mode. + // Show the `appBar`. + appBar.show(); + + // Show the `BannerAd` in the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. + BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); + + // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. + adView = findViewById(R.id.adView); + } + + // Remove the translucent navigation bar flag if it is set. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + + // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`. + rootCoordinatorLayout.setSystemUiVisibility(0); + + // Constrain `rootCoordinatorLayout` inside the status and navigation bars. + rootCoordinatorLayout.setFitsSystemWindows(true); + } + + // Consume the double-tap. + return true; + } else { // Do not consume the double-tap because full screen browsing mode is disabled. + return false; + } + } + }); + + // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps. + mainWebView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + // Send the `event` to `gestureDetector`. + return gestureDetector.onTouchEvent(event); + } + }); + + // Update `findOnPageCountTextView`. + mainWebView.setFindListener(new WebView.FindListener() { + // Get a handle for `findOnPageCountTextView`. + final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview); + + @Override + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) { + if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches. + // Set `findOnPageCountTextView` to `0/0`. + findOnPageCountTextView.setText(R.string.zero_of_zero); + } else if (isDoneCounting) { // There are matches. + // `activeMatchOrdinal` is zero-based. + int activeMatch = activeMatchOrdinal + 1; + + // Set `findOnPageCountTextView`. + findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches); + } + } + }); + + // Search for the string on the page whenever a character changes in the `findOnPageEditText`. + findOnPageEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Do nothing. + } + + @Override + public void afterTextChanged(Editable s) { + // Search for the text in `mainWebView`. + mainWebView.findAllAsync(findOnPageEditText.getText().toString()); + } + }); + + // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard. + findOnPageEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed. + // Hide the soft keyboard. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + + // Consume the event. + return true; + } else { // A different key was pressed. + // Do not consume the event. + return false; + } + } + }); + + // Implement swipe to refresh + swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout); + swipeRefreshLayout.setColorSchemeResources(R.color.blue_700); + swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + mainWebView.reload(); + } + }); + + // `DrawerTitle` identifies the `DrawerLayout` in accessibility mode. + drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer)); + + // Listen for touches on the navigation menu. + final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationview); + navigationView.setNavigationItemSelectedListener(this); + + // Get handles for `navigationMenu` and the back and forward menu items. The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu. + final Menu navigationMenu = navigationView.getMenu(); + final MenuItem navigationBackMenuItem = navigationMenu.getItem(1); + final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2); + final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3); + + // The `DrawerListener` allows us to update the Navigation Menu. + drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + } + + @Override + public void onDrawerOpened(View drawerView) { + } + + @Override + public void onDrawerClosed(View drawerView) { + } + + @Override + public void onDrawerStateChanged(int newState) { + // Update the `Back`, `Forward`, and `History` menu items every time the drawer opens. + navigationBackMenuItem.setEnabled(mainWebView.canGoBack()); + navigationForwardMenuItem.setEnabled(mainWebView.canGoForward()); + navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward())); + + // Hide the keyboard so we can see the navigation menu. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + } + }); + + // drawerToggle creates the hamburger icon at the start of the AppBar. + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer); + + // Initialize `adServerSet`. + final Set adServersSet = new HashSet<>(); + + // Load the list of ad servers into memory. + try { + // Load `pgl.yoyo.org_adservers.txt` into a `BufferedReader`. + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("pgl.yoyo.org_adservers.txt"))); + + // Create a string for storing each ad server. + String adServer; + + // Populate `adServersSet`. + while ((adServer = bufferedReader.readLine()) != null) { + adServersSet.add(adServer); + } + + // Close `bufferedReader`. + bufferedReader.close(); + } catch (IOException ioException) { + // We're pretty sure the asset exists, so we don't need to worry about the `IOException` ever being thrown. + } + + mainWebView.setWebViewClient(new WebViewClient() { + // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps. + // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24. + @SuppressWarnings("deprecation") + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + // Use an external email program if the link begins with `mailto:`. + if (url.startsWith("mailto:")) { + // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. + Intent emailIntent = new Intent(Intent.ACTION_SENDTO); + + // Parse the url and set it as the data for the `Intent`. + emailIntent.setData(Uri.parse(url)); + + // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. + emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Make it so. + startActivity(emailIntent); + return true; + } else { // Load the URL in Privacy Browser. + mainWebView.loadUrl(url, customHeaders); + return true; + } + } + + // Block ads. We have to use the deprecated `shouldInterceptRequest` until minimum API >= 21. + @SuppressWarnings("deprecation") + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, String url){ + if (adBlockerEnabled) { // Block ads. + // Extract the host from `url`. + Uri requestUri = Uri.parse(url); + String requestHost = requestUri.getHost(); + + // Create a variable to track if this is an ad server. + boolean requestHostIsAdServer = false; + + // Check all the subdomains of `requestHost` if it is not `null`. + if (requestHost != null) { + while (requestHost.contains(".")) { + if (adServersSet.contains(requestHost)) { + requestHostIsAdServer = true; + } + + // Strip out the lowest subdomain of `requestHost`. + requestHost = requestHost.substring(requestHost.indexOf(".") + 1); + } + } + + if (requestHostIsAdServer) { // It is an ad server. + // Return an empty `WebResourceResponse`. + return new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes())); + } else { // It is not an ad server. + // `return null` loads the requested resource. + return null; + } + } else { // Ad blocking is disabled. + // `return null` loads the requested resource. + return null; + } + } + + // Update the URL in urlTextBox when the page starts to load. + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + // Check to see if we are waiting on Orbot. + if (pendingUrl.isEmpty()) { // We are not waiting on Orbot, so we need to process the URL. + // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded. + formattedUrlString = url; + + // Display the loading URL is the URL text box. + urlTextBox.setText(url); + } + } + + // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load. + @Override + public void onPageFinished(WebView view, String url) { + // Check to see if we are waiting on Orbot. + if (pendingUrl.isEmpty()) { // we are not waiting on Orbot, so we need to process the URL. + formattedUrlString = url; + + // Only update urlTextBox if the user is not typing in it. + if (!urlTextBox.hasFocus()) { + urlTextBox.setText(formattedUrlString); + } + + // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog`. + sslCertificate = mainWebView.getCertificate(); + } + } + + // Handle SSL Certificate errors. + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`. + sslErrorHandler = handler; + + // Display the SSL error `AlertDialog`. + AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error); + sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); + } + }); + + mainWebView.setWebChromeClient(new WebChromeClient() { + // Update the progress bar when a page is loading. + @Override + public void onProgressChanged(WebView view, int progress) { + ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar); + progressBar.setProgress(progress); + if (progress < 100) { + progressBar.setVisibility(View.VISIBLE); + } else { + progressBar.setVisibility(View.GONE); + + //Stop the `SwipeToRefresh` indicator if it is running + swipeRefreshLayout.setRefreshing(false); + } + } + + // Set the favorite icon when it changes. + @Override + public void onReceivedIcon(WebView view, Bitmap icon) { + // Save a copy of the favorite icon for use if a shortcut is added to the home screen. + favoriteIcon = icon; + + // Place the favorite icon in the appBar. + ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon); + imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true)); + } + + // Enter full screen video + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + // Pause the ad if this is the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + BannerAd.pauseAd(adView); + } + + // Remove the translucent overlays. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. + drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + + /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. + * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. + * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown. + */ + rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + // Set `rootCoordinatorLayout` to fill the entire screen. + rootCoordinatorLayout.setFitsSystemWindows(false); + + // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen. + fullScreenVideoFrameLayout.addView(view); + fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); + } + + // Exit full screen video + public void onHideCustomView() { + // Hide `fullScreenVideoFrameLayout`. + fullScreenVideoFrameLayout.removeAllViews(); + fullScreenVideoFrameLayout.setVisibility(View.GONE); + + // Add the translucent status flag. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Set `rootCoordinatorLayout` to fit inside the status and navigation bars. This also clears the `SYSTEM_UI` flags. + rootCoordinatorLayout.setFitsSystemWindows(true); + + // Show the ad if this is the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. + BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); + + // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. + adView = findViewById(R.id.adView); + } + } + }); + + // Register `mainWebView` for a context menu. This is used to see link targets and download images. + registerForContextMenu(mainWebView); + + // Allow the downloading of files. + mainWebView.setDownloadListener(new DownloadListener() { + @Override + public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) { + // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`. + AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength); + downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + } + }); + + // Allow pinch to zoom. + mainWebView.getSettings().setBuiltInZoomControls(true); + + // Hide zoom controls. + mainWebView.getSettings().setDisplayZoomControls(false); + + // Initialize cookieManager. + cookieManager = CookieManager.getInstance(); + + // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard). + customHeaders.put("X-Requested-With", ""); + + // Initialize the default preference values the first time the program is run. `this` is the context. `false` keeps this command from resetting any current preferences back to default. + PreferenceManager.setDefaultValues(this, R.xml.preferences, false); + + // Get the intent that started the app. + final Intent launchingIntent = getIntent(); + + // Extract the launching intent data as `launchingIntentUriData`. + final Uri launchingIntentUriData = launchingIntent.getData(); + + // Convert the launching intent URI data (if it exists) to a string and store it in `formattedUrlString`. + if (launchingIntentUriData != null) { + formattedUrlString = launchingIntentUriData.toString(); + } + + // Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode. + inFullScreenBrowsingMode = false; + + // Initialize AdView for the free flavor. + adView = findViewById(R.id.adView); + + // Apply the settings from the shared preferences. + applySettings(); + + // Load `formattedUrlString` if we are not proxying through Orbot and waiting for Orbot to connect. + if (!(proxyThroughOrbot && !orbotStatus.equals("ON"))) { + mainWebView.loadUrl(formattedUrlString, customHeaders); + } + + // If the favorite icon is null, load the default. + if (favoriteIcon == null) { + // We have to use `ContextCompat` until API >= 21. + Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world); + BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable; + favoriteIcon = favoriteIconBitmapDrawable.getBitmap(); + } + } + + + @Override + protected void onNewIntent(Intent intent) { + // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity. + setIntent(intent); + + if (intent.getData() != null) { + // Get the intent data and convert it to a string. + final Uri intentUriData = intent.getData(); + formattedUrlString = intentUriData.toString(); + } + + // Close the navigation drawer if it is open. + if (drawerLayout.isDrawerVisible(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START); + } + + // Load the website. + mainWebView.loadUrl(formattedUrlString, customHeaders); + + // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it. + mainWebView.requestFocus(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.webview_options_menu, menu); + + // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`. + mainMenu = menu; + + // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(false); + + // Get handles for the menu items. + MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies); + MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies); + MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage); + MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData); + + // Only display third-Party Cookies if SDK >= 21 + toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21); + + // Get the shared preference values. `this` references the current context. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Set the status of the additional app bar icons. The default is `false`. + if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) { + toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } else { //Do not display the additional icons. + toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + } + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + // Get handles for the menu items. + MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies); + MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies); + MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage); + MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData); + MenuItem clearCookies = menu.findItem(R.id.clearCookies); + MenuItem clearFormData = menu.findItem(R.id.clearFormData); + MenuItem refreshMenuItem = menu.findItem(R.id.refresh); + + // Set the status of the menu item checkboxes. + toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled); + toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled); + toggleDomStorage.setChecked(domStorageEnabled); + toggleSaveFormData.setChecked(saveFormDataEnabled); + + // Enable third-party cookies if first-party cookies are enabled. + toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled); + + // Enable DOM Storage if JavaScript is enabled. + toggleDomStorage.setEnabled(javaScriptEnabled); + + // Enable Clear Cookies if there are any. + clearCookies.setEnabled(cookieManager.hasCookies()); + + // Enable Clear Form Data is there is any. + WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); + clearFormData.setEnabled(mainWebViewDatabase.hasFormData()); + + // Only show `Refresh` if `swipeToRefresh` is disabled. + refreshMenuItem.setVisible(!swipeToRefreshEnabled); + + // Initialize font size variables. + int fontSize = mainWebView.getSettings().getTextZoom(); + String fontSizeTitle; + MenuItem selectedFontSizeMenuItem; + + // Prepare the font size title and current size menu item. + switch (fontSize) { + case 50: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent); + break; + + case 75: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent); + break; + + case 100: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); + break; + + case 125: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent); + break; + + case 150: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent); + break; + + case 175: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent); + break; + + case 200: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent); + break; + + default: + fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent); + selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent); + break; + } + + // Set the font size title and select the current size menu item. + MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize); + fontSizeMenuItem.setTitle(fontSizeTitle); + selectedFontSizeMenuItem.setChecked(true); + + // Run all the other default commands. + super.onPrepareOptionsMenu(menu); + + // `return true` displays the menu. + return true; + } + + @Override + // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. + @SuppressLint("SetJavaScriptEnabled") + // removeAllCookies is deprecated, but it is required for API < 21. + @SuppressWarnings("deprecation") + public boolean onOptionsItemSelected(MenuItem menuItem) { + int menuItemId = menuItem.getItemId(); + + // Set the commands that relate to the menu entries. + switch (menuItemId) { + case R.id.toggleJavaScript: + // Switch the status of javaScriptEnabled. + javaScriptEnabled = !javaScriptEnabled; + + // Apply the new JavaScript status. + mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(true); + + // Display a `Snackbar`. + if (javaScriptEnabled) { // JavaScrip is enabled. + Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show(); + } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled. + Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show(); + } else { // Privacy mode. + Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); + } + + // Reload the WebView. + mainWebView.reload(); + return true; + + case R.id.toggleFirstPartyCookies: + // Switch the status of firstPartyCookiesEnabled. + firstPartyCookiesEnabled = !firstPartyCookiesEnabled; + + // Update the menu checkbox. + menuItem.setChecked(firstPartyCookiesEnabled); + + // Apply the new cookie status. + cookieManager.setAcceptCookie(firstPartyCookiesEnabled); + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(true); + + // Display a `Snackbar`. + if (firstPartyCookiesEnabled) { // First-party cookies are enabled. + Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); + } else if (javaScriptEnabled){ // JavaScript is still enabled. + Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); + } else { // Privacy mode. + Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show(); + } + + // Reload the WebView. + mainWebView.reload(); + return true; + + case R.id.toggleThirdPartyCookies: + if (Build.VERSION.SDK_INT >= 21) { + // Switch the status of thirdPartyCookiesEnabled. + thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled; + + // Update the menu checkbox. + menuItem.setChecked(thirdPartyCookiesEnabled); + + // Apply the new cookie status. + cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); + + // Display a `Snackbar`. + if (thirdPartyCookiesEnabled) { + Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show(); + } + + // Reload the WebView. + mainWebView.reload(); + } // Else do nothing because SDK < 21. + return true; + + case R.id.toggleDomStorage: + // Switch the status of domStorageEnabled. + domStorageEnabled = !domStorageEnabled; + + // Update the menu checkbox. + menuItem.setChecked(domStorageEnabled); + + // Apply the new DOM Storage status. + mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(true); + + // Display a `Snackbar`. + if (domStorageEnabled) { + Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show(); + } + + // Reload the WebView. + mainWebView.reload(); + return true; + + case R.id.toggleSaveFormData: + // Switch the status of saveFormDataEnabled. + saveFormDataEnabled = !saveFormDataEnabled; + + // Update the menu checkbox. + menuItem.setChecked(saveFormDataEnabled); + + // Apply the new form data status. + mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); + + // Display a `Snackbar`. + if (saveFormDataEnabled) { + Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show(); + } else { + Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show(); + } + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(true); + + // Reload the WebView. + mainWebView.reload(); + return true; + + case R.id.clearCookies: + if (Build.VERSION.SDK_INT < 21) { + cookieManager.removeAllCookie(); + } else { + cookieManager.removeAllCookies(null); + } + Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show(); + return true; + + case R.id.clearDomStorage: + WebStorage webStorage = WebStorage.getInstance(); + webStorage.deleteAllData(); + Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show(); + return true; + + case R.id.clearFormData: + WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this); + mainWebViewDatabase.clearFormData(); + Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_deleted, Snackbar.LENGTH_SHORT).show(); + return true; + + case R.id.fontSizeFiftyPercent: + mainWebView.getSettings().setTextZoom(50); + return true; + + case R.id.fontSizeSeventyFivePercent: + mainWebView.getSettings().setTextZoom(75); + return true; + + case R.id.fontSizeOneHundredPercent: + mainWebView.getSettings().setTextZoom(100); + return true; + + case R.id.fontSizeOneHundredTwentyFivePercent: + mainWebView.getSettings().setTextZoom(125); + return true; + + case R.id.fontSizeOneHundredFiftyPercent: + mainWebView.getSettings().setTextZoom(150); + return true; + + case R.id.fontSizeOneHundredSeventyFivePercent: + mainWebView.getSettings().setTextZoom(175); + return true; + + case R.id.fontSizeTwoHundredPercent: + mainWebView.getSettings().setTextZoom(200); + return true; + + case R.id.find_on_page: + // Hide the URL app bar. + supportAppBar.setVisibility(View.GONE); + + // Show the Find on Page `RelativeLayout`. + findOnPageLinearLayout.setVisibility(View.VISIBLE); + + // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android. + // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working + findOnPageEditText.postDelayed(new Runnable() + { + @Override + public void run() + { + // Set the focus on `findOnPageEditText`. + findOnPageEditText.requestFocus(); + + // Display the keyboard. + inputMethodManager.showSoftInput(findOnPageEditText, 0); + } + }, 200); + return true; + + case R.id.share: + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString()); + shareIntent.setType("text/plain"); + startActivity(Intent.createChooser(shareIntent, "Share URL")); + return true; + + case R.id.addToHomescreen: + // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`. + AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog(); + createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut)); + + //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below. + return true; + + case R.id.print: + // Get a `PrintManager` instance. + PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); + + // Convert `mainWebView` to `printDocumentAdapter`. + PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter(); + + // Print the document. The print attributes are `null`. + printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null); + return true; + + case R.id.refresh: + mainWebView.reload(); + return true; + + default: + // Don't consume the event. + return super.onOptionsItemSelected(menuItem); + } + } + + // removeAllCookies is deprecated, but it is required for API < 21. + @SuppressWarnings("deprecation") + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { + int menuItemId = menuItem.getItemId(); + + switch (menuItemId) { + case R.id.home: + mainWebView.loadUrl(homepage, customHeaders); + break; + + case R.id.back: + if (mainWebView.canGoBack()) { + mainWebView.goBack(); + } + break; + + case R.id.forward: + if (mainWebView.canGoForward()) { + mainWebView.goForward(); + } + break; + + case R.id.history: + // Gte the `WebBackForwardList`. + WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList(); + + // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`. + AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList); + urlHistoryDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.history)); + 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); + + // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list. + downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + startActivity(downloadManagerIntent); + break; + + case R.id.settings: + // Launch `SettingsActivity`. + Intent settingsIntent = new Intent(this, SettingsActivity.class); + startActivity(settingsIntent); + break; + + case R.id.domains: + // Launch `DomainsActivity`. + Intent domainsIntent = new Intent(this, DomainsActivity.class); + startActivity(domainsIntent); + 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); + startActivity(aboutIntent); + break; + + case R.id.clearAndExit: + // Clear cookies. The commands changed slightly in API 21. + if (Build.VERSION.SDK_INT >= 21) { + cookieManager.removeAllCookies(null); + } else { + cookieManager.removeAllCookie(); + } + + // Clear DOM storage. + WebStorage domStorage = WebStorage.getInstance(); + domStorage.deleteAllData(); + + // Clear form data. + WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this); + webViewDatabase.clearFormData(); + + // Clear cache. The argument of "true" includes disk files. + mainWebView.clearCache(true); + + // Clear the back/forward history. + mainWebView.clearHistory(); + + // Clear any SSL certificate preferences. + mainWebView.clearSslPreferences(); + + // Clear `formattedUrlString`. + formattedUrlString = null; + + // Clear `customHeaders`. + customHeaders.clear(); + + // Detach all views from `mainWebViewRelativeLayout`. + mainWebViewRelativeLayout.removeAllViews(); + + // Destroy the internal state of `mainWebView`. + mainWebView.destroy(); + + // Manually delete the `app_webview` folder, which contains an additional `WebView` cache. See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`. + Runtime runtime = Runtime.getRuntime(); + String dataDirString = getApplicationInfo().dataDir; // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`. + try { + runtime.exec("rm -rf " + dataDirString + "/app_webview"); + } catch (IOException e) { + // Do nothing if the files do not exist. + } + + // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. + if (Build.VERSION.SDK_INT >= 21) { + finishAndRemoveTask(); + } else { + finish(); + } + + // Remove the terminated program from RAM. The status code is `0`. + System.exit(0); + break; + + default: + break; + } + + // Close the navigation drawer. + drawerLayout.closeDrawer(GravityCompat.START); + return true; + } + + @Override + public void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + + // Sync the state of the DrawerToggle after onRestoreInstanceState has finished. + drawerToggle.syncState(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Reload the ad for the free flavor if we are not in full screen mode. + if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) { + // Reload the ad. + BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); + + // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. + adView = findViewById(R.id.adView); + } + + // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug: https://code.google.com/p/android/issues/detail?id=20493#c8 + // ActivityCompat.invalidateOptionsMenu(this); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + // Store the `HitTestResult`. + final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult(); + + // Create strings. + final String imageUrl; + final String linkUrl; + + // Get a handle for the `ClipboardManager`. + final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + + switch (hitTestResult.getType()) { + // `SRC_ANCHOR_TYPE` is a link. + case WebView.HitTestResult.SRC_ANCHOR_TYPE: + // Get the target URL. + linkUrl = hitTestResult.getExtra(); + + // Set the target URL as the title of the `ContextMenu`. + menu.setHeaderTitle(linkUrl); + + // Add a `Load URL` entry. + menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + mainWebView.loadUrl(linkUrl, customHeaders); + return false; + } + }); + + // Add a `Copy URL` entry. + menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Save the link URL in a `ClipData`. + ClipData srcAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), linkUrl); + + // Set the `ClipData` as the clipboard's primary clip. + clipboardManager.setPrimaryClip(srcAnchorTypeClipData); + return false; + } + }); + + // Add a `Cancel` entry, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + + case WebView.HitTestResult.EMAIL_TYPE: + // Get the target URL. + linkUrl = hitTestResult.getExtra(); + + // Set the target URL as the title of the `ContextMenu`. + menu.setHeaderTitle(linkUrl); + + // Add a `Write Email` entry. + menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. + Intent emailIntent = new Intent(Intent.ACTION_SENDTO); + + // Parse the url and set it as the data for the `Intent`. + emailIntent.setData(Uri.parse("mailto:" + linkUrl)); + + // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. + emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Make it so. + startActivity(emailIntent); + return false; + } + }); + + // Add a `Copy Email Address` entry. + menu.add(R.string.copy_email_address).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Save the email address in a `ClipData`. + ClipData srcEmailTypeClipData = ClipData.newPlainText(getResources().getString(R.string.email_address), linkUrl); + + // Set the `ClipData` as the clipboard's primary clip. + clipboardManager.setPrimaryClip(srcEmailTypeClipData); + return false; + } + }); + + // Add a `Cancel` entry, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + + // `IMAGE_TYPE` is an image. + case WebView.HitTestResult.IMAGE_TYPE: + // Get the image URL. + imageUrl = hitTestResult.getExtra(); + + // Set the image URL as the title of the `ContextMenu`. + menu.setHeaderTitle(imageUrl); + + // Add a `View Image` entry. + menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + mainWebView.loadUrl(imageUrl, customHeaders); + return false; + } + }); + + // Add a `Download Image` entry. + menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + return false; + } + }); + + // Add a `Copy URL` entry. + menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Save the image URL in a `ClipData`. + ClipData srcImageTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl); + + // Set the `ClipData` as the clipboard's primary clip. + clipboardManager.setPrimaryClip(srcImageTypeClipData); + return false; + } + }); + + // Add a `Cancel` entry, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + + + // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. + case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: + // Get the image URL. + imageUrl = hitTestResult.getExtra(); + + // Set the image URL as the title of the `ContextMenu`. + menu.setHeaderTitle(imageUrl); + + // Add a `View Image` entry. + menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + mainWebView.loadUrl(imageUrl, customHeaders); + return false; + } + }); + + // Add a `Download Image` entry. + menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl); + downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + return false; + } + }); + + // Add a `Copy URL` entry. + menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Save the image URL in a `ClipData`. + ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl); + + // Set the `ClipData` as the clipboard's primary clip. + clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData); + return false; + } + }); + + // Add a `Cancel` entry, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + } + } + + @Override + public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) { + // Get shortcutNameEditText from the alert dialog. + EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext); + + // Create the bookmark shortcut based on formattedUrlString. + Intent bookmarkShortcut = new Intent(); + bookmarkShortcut.setAction(Intent.ACTION_VIEW); + bookmarkShortcut.setData(Uri.parse(formattedUrlString)); + + // Place the bookmark shortcut on the home screen. + Intent placeBookmarkShortcut = new Intent(); + placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut); + placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString()); + placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon); + placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + sendBroadcast(placeBookmarkShortcut); + } + + @Override + public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) { + // Download the image if it has an HTTP or HTTPS URI. + if (imageUrl.startsWith("http")) { + // Get a handle for the system `DOWNLOAD_SERVICE`. + DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); + + // Parse `imageUrl`. + DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl)); + + // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login. + if (firstPartyCookiesEnabled) { + // Get the cookies for `imageUrl`. + String cookies = cookieManager.getCookie(imageUrl); + + // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`. + downloadRequest.addRequestHeader("Cookie", cookies); + } + + // Get the file name from `dialogFragment`. + EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name); + String imageName = downloadImageNameEditText.getText().toString(); + + // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`. + if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`. + downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName); + } else { // Only set the title using `imageName`. + downloadRequest.setTitle(imageName); + } + + // Allow `MediaScanner` to index the download if it is a media file. + downloadRequest.allowScanningByMediaScanner(); + + // Add the URL as the description for the download. + downloadRequest.setDescription(imageUrl); + + // Show the download notification after the download is completed. + downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + + // Initiate the download. + downloadManager.enqueue(downloadRequest); + } else { // The image is not an HTTP or HTTPS URI. + Snackbar.make(mainWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show(); + } + } + + @Override + public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) { + // Download the file if it has an HTTP or HTTPS URI. + if (downloadUrl.startsWith("http")) { + + // Get a handle for the system `DOWNLOAD_SERVICE`. + DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); + + // Parse `downloadUrl`. + DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl)); + + // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login. + if (firstPartyCookiesEnabled) { + // Get the cookies for `downloadUrl`. + String cookies = cookieManager.getCookie(downloadUrl); + + // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`. + downloadRequest.addRequestHeader("Cookie", cookies); + } + + // Get the file name from `dialogFragment`. + EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name); + String fileName = downloadFileNameEditText.getText().toString(); + + // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`. + if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download location to `/sdcard/Android/data/com.stoutner.privacybrowser.standard/files` named `fileName`. + downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName); + } else { // Only set the title using `fileName`. + downloadRequest.setTitle(fileName); + } + + // Allow `MediaScanner` to index the download if it is a media file. + downloadRequest.allowScanningByMediaScanner(); + + // Add the URL as the description for the download. + downloadRequest.setDescription(downloadUrl); + + // Show the download notification after the download is completed. + downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + + // Initiate the download. + downloadManager.enqueue(downloadRequest); + } else { // The download is not an HTTP or HTTPS URI. + Snackbar.make(mainWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show(); + } + } + + public void viewSslCertificate(View view) { + // Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`. + DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog(); + viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate)); + } + + @Override + public void onSslErrorCancel() { + sslErrorHandler.cancel(); + } + + @Override + public void onSslErrorProceed() { + sslErrorHandler.proceed(); + } + + @Override + public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) { + // Load the history entry. + mainWebView.goBackOrForward(moveBackOrForwardSteps); + } + + @Override + public void onClearHistory() { + // Clear the history. + mainWebView.clearHistory(); + } + + // Override `onBackPressed` to handle the navigation drawer and `mainWebView`. + @Override + public void onBackPressed() { + // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text. + if (drawerLayout.isDrawerVisible(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START); + } else { + // Load the previous URL if available. + if (mainWebView.canGoBack()) { + mainWebView.goBack(); + } else { + // Pass `onBackPressed()` to the system. + super.onBackPressed(); + } + } + } + + @Override + public void onPause() { + // Pause `mainWebView`. + mainWebView.onPause(); + + // Stop all JavaScript. + mainWebView.pauseTimers(); + + // Pause the adView or it will continue to consume resources in the background on the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + BannerAd.pauseAd(adView); + } + + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + + // Resume JavaScript (if enabled). + mainWebView.resumeTimers(); + + // Resume `mainWebView`. + mainWebView.onResume(); + + // Resume the adView for the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + BannerAd.resumeAd(adView); + } + } + + @Override + public void onRestart() { + super.onRestart(); + + // Apply the settings from shared preferences, which might have been changed in `SettingsActivity`. + applySettings(); + + // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. + updatePrivacyIcons(true); + + } + + private void loadUrlFromTextBox() throws UnsupportedEncodingException { + // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string. + String unformattedUrlString = urlTextBox.getText().toString().trim(); + + URL unformattedUrl = null; + Uri.Builder formattedUri = new Uri.Builder(); + + // Check to see if unformattedUrlString is a valid URL. Otherwise, convert it into a Duck Duck Go search. + if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) { + // Add http:// at the beginning if it is missing. Otherwise the app will segfault. + if (!unformattedUrlString.startsWith("http")) { + unformattedUrlString = "http://" + unformattedUrlString; + } + + // Convert unformattedUrlString to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components. + try { + unformattedUrl = new URL(unformattedUrlString); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + + // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value. + final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null; + final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null; + final String path = unformattedUrl != null ? unformattedUrl.getPath() : null; + final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null; + final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null; + + formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment); + formattedUrlString = formattedUri.build().toString(); + } else { + // Sanitize the search input and convert it to a DuckDuckGo search. + final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8"); + + // Use the correct search URL. + if (javaScriptEnabled) { // JavaScript is enabled. + formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString; + } else { // JavaScript is disabled. + formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString; + } + } + + mainWebView.loadUrl(formattedUrlString, customHeaders); + + // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + } + + public void findPreviousOnPage(View view) { + // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. + mainWebView.findNext(false); + } + + public void findNextOnPage(View view) { + // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. + mainWebView.findNext(true); + } + + public void closeFindOnPage(View view) { + // Delete the contents of `find_on_page_edittext`. + findOnPageEditText.setText(null); + + // Clear the highlighted phrases. + mainWebView.clearMatches(); + + // Hide the Find on Page `RelativeLayout`. + findOnPageLinearLayout.setVisibility(View.GONE); + + // Show the URL app bar. + supportAppBar.setVisibility(View.VISIBLE); + + // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + } + + private void applySettings() { + // Get the shared preference values. `this` references the current context. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + + // Store the values from `sharedPreferences` in variables. + String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0"); + String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); + String javaScriptDisabledSearchCustomURLString = sharedPreferences.getString("javascript_disabled_search_custom_url", ""); + String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); + String javaScriptEnabledSearchCustomURLString = sharedPreferences.getString("javascript_enabled_search_custom_url", ""); + String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com"); + String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion"); + String torJavaScriptDisabledSearchString = sharedPreferences.getString("tor_javascript_disabled_search", "https://3g2upl4pq6kufc4m.onion/html/?q="); + String torJavaScriptDisabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_disabled_search_custom_url", ""); + String torJavaScriptEnabledSearchString = sharedPreferences.getString("tor_javascript_enabled_search", "https://3g2upl4pq6kufc4m.onion/?q="); + String torJavaScriptEnabledSearchCustomURLString = sharedPreferences.getString("tor_javascript_enabled_search_custom_url", ""); + String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); + swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false); + adBlockerEnabled = sharedPreferences.getBoolean("block_ads", true); + boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false); + proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); + fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("enable_full_screen_browsing_mode", false); + hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false); + translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true); + + // Because they can be modified on-the-fly by the user, these default settings are only applied when the program first runs. + if (javaScriptEnabled == null) { // If `javaScriptEnabled` is null the program is just starting. + // Get the values from `sharedPreferences`. + javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false); + firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false); + thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false); + domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false); + saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false); + + // Apply the default settings. + mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled); + cookieManager.setAcceptCookie(firstPartyCookiesEnabled); + mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled); + mainWebView.getSettings().setSaveFormData(saveFormDataEnabled); + mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString)); + + // Set third-party cookies status if API >= 21. + if (Build.VERSION.SDK_INT >= 21) { + cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled); + } + } + + // Set the homepage, search, and proxy options. + if (proxyThroughOrbot) { // Set the Tor options. + // Set `torHomepageString` as `homepage`. + homepage = torHomepageString; + + // If formattedUrlString is null assign the homepage to it. + if (formattedUrlString == null) { + formattedUrlString = homepage; + } + + // Set JavaScript disabled search. + if (torJavaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string. + javaScriptDisabledSearchURL = torJavaScriptDisabledSearchCustomURLString; + } else { // Use the string from the pre-built list. + javaScriptDisabledSearchURL = torJavaScriptDisabledSearchString; + } + + // Set JavaScript enabled search. + if (torJavaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string. + javaScriptEnabledSearchURL = torJavaScriptEnabledSearchCustomURLString; + } else { // Use the string from the pre-built list. + javaScriptEnabledSearchURL = torJavaScriptEnabledSearchString; + } + + // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed. + OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118"); + + // Display a message to the user if we are waiting on Orbot. + if (!orbotStatus.equals("ON")) { + // Save `formattedUrlString` in `pendingUrl`. + pendingUrl = formattedUrlString; + + // Load a waiting page. `null` specifies no encoding, which defaults to ASCII. + mainWebView.loadData(waitingForOrbotHTMLString, "text/html", null); + } + } else { // Set the non-Tor options. + // Set `homepageString` as `homepage`. + homepage = homepageString; + + // If formattedUrlString is null assign the homepage to it. + if (formattedUrlString == null) { + formattedUrlString = homepage; + } + + // Set JavaScript disabled search. + if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string. + javaScriptDisabledSearchURL = javaScriptDisabledSearchCustomURLString; + } else { // Use the string from the pre-built list. + javaScriptDisabledSearchURL = javaScriptDisabledSearchString; + } + + // Set JavaScript enabled search. + if (javaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string. + javaScriptEnabledSearchURL = javaScriptEnabledSearchCustomURLString; + } else { // Use the string from the pre-built list. + javaScriptEnabledSearchURL = javaScriptEnabledSearchString; + } + + // Reset the proxy to default. The host is `""` and the port is `"0"`. + OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0"); + + // Reset `pendingUrl` if we are currently waiting for Orbot to connect. + if (!pendingUrl.isEmpty()) { + formattedUrlString = pendingUrl; + pendingUrl = ""; + } + } + + // Set swipe to refresh. + swipeRefreshLayout.setEnabled(swipeToRefreshEnabled); + + // Set the user agent initial status. + switch (userAgentString) { + case "Default user agent": + // Set the user agent to `""`, which uses the default value. + mainWebView.getSettings().setUserAgentString(""); + break; + + case "Custom user agent": + // Set the custom user agent. + mainWebView.getSettings().setUserAgentString(customUserAgentString); + break; + + default: + // Use the selected user agent. + mainWebView.getSettings().setUserAgentString(userAgentString); + break; + } + + // Set Do Not Track status. + if (doNotTrackEnabled) { + customHeaders.put("DNT", "1"); + } else { + customHeaders.remove("DNT"); + } + + // Apply the appropriate full screen mode the `SYSTEM_UI` flags. + if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { + if (hideSystemBarsOnFullscreen) { // Hide everything. + // Remove the translucent navigation setting if it is currently flagged. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + + // Remove the translucent status bar overlay. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command. + drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + + /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen. + * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen. + * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown. + */ + rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } else { // Hide everything except the status and navigation bars. + // Add the translucent status flag if it is unset. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + if (translucentNavigationBarOnFullscreen) { + // Set the navigation bar to be translucent. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } else { + // Set the navigation bar to be black. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + } + } + } else { // Switch to normal viewing mode. + // Reset `inFullScreenBrowsingMode` to `false`. + inFullScreenBrowsingMode = false; + + // Show the `appBar`. + appBar.show(); + + // Show the `BannerAd` in the free flavor. + if (BuildConfig.FLAVOR.contentEquals("free")) { + // Reload the ad. Because the screen may have rotated, we need to use `reloadAfterRotate`. + BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); + + // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`. + adView = findViewById(R.id.adView); + } + + // Remove the translucent navigation bar flag if it is set. + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + + // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`. + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + + // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`. + rootCoordinatorLayout.setSystemUiVisibility(0); + + // Constrain `rootCoordinatorLayout` inside the status and navigation bars. + rootCoordinatorLayout.setFitsSystemWindows(true); + } + } + + private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) { + // Get handles for the icons. + MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript); + MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies); + MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage); + MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData); + + // Update `privacyIcon`. + if (javaScriptEnabled) { // JavaScript is enabled. + privacyIcon.setIcon(R.drawable.javascript_enabled); + } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled. + privacyIcon.setIcon(R.drawable.warning); + } else { // All the dangerous features are disabled. + privacyIcon.setIcon(R.drawable.privacy_mode); + } + + // Update `firstPartyCookiesIcon`. + if (firstPartyCookiesEnabled) { // First-party cookies are enabled. + firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled); + } else { // First-party cookies are disabled. + firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled); + } + + // Update `domStorageIcon`. + if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled. + domStorageIcon.setIcon(R.drawable.dom_storage_enabled); + } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled. + domStorageIcon.setIcon(R.drawable.dom_storage_disabled); + } else { // JavaScript is disabled, so DOM storage is ghosted. + domStorageIcon.setIcon(R.drawable.dom_storage_ghosted); + } + + // Update `formDataIcon`. + if (saveFormDataEnabled) { // Form data is enabled. + formDataIcon.setIcon(R.drawable.form_data_enabled); + } else { // Form data is disabled. + formDataIcon.setIcon(R.drawable.form_data_disabled); + } + + // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity. + if (runInvalidateOptionsMenu) { + ActivityCompat.invalidateOptionsMenu(this); + } + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/Settings.java b/app/src/main/java/com/stoutner/privacybrowser/activities/Settings.java deleted file mode 100644 index 027482c7..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/Settings.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2016 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.activities; - -import android.os.Bundle; -import android.preference.PreferenceFragment; -import android.support.v7.app.AppCompatActivity; - -import com.stoutner.privacybrowser.fragments.SettingsFragment; - -public class Settings extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Display SettingsFragment. - PreferenceFragment settingsFragment = new SettingsFragment(); - getFragmentManager().beginTransaction().replace(android.R.id.content, settingsFragment).commit(); - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/SettingsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/SettingsActivity.java new file mode 100644 index 00000000..ddeb7b7b --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/SettingsActivity.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.activities; + +import android.os.Bundle; +import android.preference.PreferenceFragment; +import android.support.v7.app.AppCompatActivity; + +import com.stoutner.privacybrowser.fragments.SettingsFragment; + +public class SettingsActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Display SettingsFragment. + PreferenceFragment settingsFragment = new SettingsFragment(); + getFragmentManager().beginTransaction().replace(android.R.id.content, settingsFragment).commit(); + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java b/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java index e5804218..180d4cce 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java +++ b/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java @@ -1,5 +1,5 @@ -/** - * Copyright 2016 Soren Stoutner . +/* + * Copyright 2016-2017 Soren Stoutner . * * This file is part of Privacy Browser . * diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomain.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomain.java deleted file mode 100644 index c176633a..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomain.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. -import android.support.annotation.NonNull; -import android.support.v7.app.AppCompatDialogFragment; -import android.view.KeyEvent; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; - -import com.stoutner.privacybrowser.R; - -public class AddDomain extends AppCompatDialogFragment { - // The public interface is used to send information back to the parent activity. - public interface AddDomainListener { - void onAddDomain(AppCompatDialogFragment dialogFragment); - } - - // `addDomainListener` is used in `onAttach()` and `onCreateDialog()`. - private AddDomainListener addDomainListener; - - - public void onAttach(Context context) { - super.onAttach(context); - - // Get a handle for `AddDomainListener` from `context`. - try { - addDomainListener = (AddDomainListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement `AddDomainListener`."); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.add_domain); - // The parent view is `null` because it will be assigned by the `AlertDialog`. - dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.add_domain_dialog, null)); - - // Set an `onClick()` listener for the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing. The `AlertDialog` will close automatically. - } - }); - - // Set an `onClick()` listener for the positive button. - dialogBuilder.setPositiveButton(R.string.add, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Return the `DialogFragment` to the parent activity on add. - addDomainListener.onAddDomain(AddDomain.this); - } - }); - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below the `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when the `AlertDialog` is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show the `AlertDialog` before w3e can call `setOnKeyListener()` below. - alertDialog.show(); - - // Allow the `enter` key on the keyboard to create the domain from `add_domain_edittext`. - EditText addDomainEditText = (EditText) alertDialog.findViewById(R.id.domain_name_edittext); - addDomainEditText.setOnKeyListener(new View.OnKeyListener() { - public boolean onKey(View view, int keyCode, KeyEvent event) { - // If the event is a key-down on the `enter` key, select the `PositiveButton` `Add`. - if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { - // Trigger `addDomainListener` and return the `DialogFragment` to the parent activity. - addDomainListener.onAddDomain(AddDomain.this); - // Manually dismiss the `AlertDialog`. - alertDialog.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 alertDialog; - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.java new file mode 100644 index 00000000..e4cf59f0 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/AddDomainDialog.java @@ -0,0 +1,120 @@ +/* + * Copyright 2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatDialogFragment; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; + +import com.stoutner.privacybrowser.R; + +public class AddDomainDialog extends AppCompatDialogFragment { + // The public interface is used to send information back to the parent activity. + public interface AddDomainListener { + void onAddDomain(AppCompatDialogFragment dialogFragment); + } + + // `addDomainListener` is used in `onAttach()` and `onCreateDialog()`. + private AddDomainListener addDomainListener; + + + public void onAttach(Context context) { + super.onAttach(context); + + // Get a handle for `AddDomainListener` from `context`. + try { + addDomainListener = (AddDomainListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement `AddDomainListener`."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.add_domain); + // The parent view is `null` because it will be assigned by the `AlertDialog`. + dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.add_domain_dialog, null)); + + // Set an `onClick()` listener for the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing. The `AlertDialog` will close automatically. + } + }); + + // Set an `onClick()` listener for the positive button. + dialogBuilder.setPositiveButton(R.string.add, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Return the `DialogFragment` to the parent activity on add. + addDomainListener.onAddDomain(AddDomainDialog.this); + } + }); + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below the `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when the `AlertDialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show the `AlertDialog` before w3e can call `setOnKeyListener()` below. + alertDialog.show(); + + // Allow the `enter` key on the keyboard to create the domain from `add_domain_edittext`. + EditText addDomainEditText = (EditText) alertDialog.findViewById(R.id.domain_name_edittext); + addDomainEditText.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View view, int keyCode, KeyEvent event) { + // If the event is a key-down on the `enter` key, select the `PositiveButton` `Add`. + if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { + // Trigger `addDomainListener` and return the `DialogFragment` to the parent activity. + addDomainListener.onAddDomain(AddDomainDialog.this); + // Manually dismiss the `AlertDialog`. + alertDialog.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 alertDialog; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmark.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmark.java deleted file mode 100644 index 5ac074b2..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmark.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. -import android.support.v7.app.AppCompatDialogFragment; -import android.view.KeyEvent; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; - -import com.stoutner.privacybrowser.activities.MainWebView; -import com.stoutner.privacybrowser.R; - -public class CreateBookmark extends AppCompatDialogFragment { - // The public interface is used to send information back to the parent activity. - public interface CreateBookmarkListener { - void onCreateBookmark(AppCompatDialogFragment dialogFragment); - } - - // `createBookmarkListener` is used in `onAttach()` and `onCreateDialog()` - private CreateBookmarkListener createBookmarkListener; - - - public void onAttach(Context context) { - super.onAttach(context); - - // Get a handle for `CreateBookmarkListener` from `context`. - try { - createBookmarkListener = (CreateBookmarkListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement `CreateBookmarkListener`."); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Create a drawable version of the favorite icon. - Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebView.favoriteIcon); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.create_bookmark); - dialogBuilder.setIcon(favoriteIconDrawable); - // The parent view is `null` because it will be assigned by the `AlertDialog`. - dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.create_bookmark_dialog, null)); - - // Set an `onClick()` listener for the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing. The `AlertDialog` will close automatically. - } - }); - - // Set an `onClick()` listener for the positive button. - dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Return the `DialogFragment` to the parent activity on create. - createBookmarkListener.onCreateBookmark(CreateBookmark.this); - } - }); - - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when the `AlertDialog` is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show the `AlertDialog` before we can call `setOnKeyListener()` below. - alertDialog.show(); - - // Allow the `enter` key on the keyboard to create the bookmark from `create_bookmark_name_edittext`. - EditText createBookmarkNameEditText = (EditText) alertDialog.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 view, int keyCode, KeyEvent event) { - // If the event is a key-down on the `enter` key, select the `PositiveButton` `Create`. - if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { - // Trigger `createBookmarkListener` and return the `DialogFragment` to the parent activity. - createBookmarkListener.onCreateBookmark(CreateBookmark.this); - // Manually dismiss the `AlertDialog`. - alertDialog.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) alertDialog.findViewById(R.id.create_bookmark_url_edittext); - assert createBookmarkUrlEditText != null;// Remove the warning below that `createBookmarkUrlEditText` might be `null`. - createBookmarkUrlEditText.setText(MainWebView.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" key, select the PositiveButton "Create". - if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { - // Trigger `createBookmarkListener` and return the DialogFragment to the parent activity. - createBookmarkListener.onCreateBookmark(CreateBookmark.this); - // Manually dismiss the `AlertDialog`. - alertDialog.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 alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java new file mode 100644 index 00000000..80f16bda --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkDialog.java @@ -0,0 +1,151 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. +import android.support.v7.app.AppCompatDialogFragment; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; + +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +import com.stoutner.privacybrowser.R; + +public class CreateBookmarkDialog extends AppCompatDialogFragment { + // The public interface is used to send information back to the parent activity. + public interface CreateBookmarkListener { + void onCreateBookmark(AppCompatDialogFragment dialogFragment); + } + + // `createBookmarkListener` is used in `onAttach()` and `onCreateDialog()` + private CreateBookmarkListener createBookmarkListener; + + + public void onAttach(Context context) { + super.onAttach(context); + + // Get a handle for `CreateBookmarkListener` from `context`. + try { + createBookmarkListener = (CreateBookmarkListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement `CreateBookmarkListener`."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Create a drawable version of the favorite icon. + Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIcon); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.create_bookmark); + dialogBuilder.setIcon(favoriteIconDrawable); + // The parent view is `null` because it will be assigned by the `AlertDialog`. + dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.create_bookmark_dialog, null)); + + // Set an `onClick()` listener for the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing. The `AlertDialog` will close automatically. + } + }); + + // Set an `onClick()` listener for the positive button. + dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Return the `DialogFragment` to the parent activity on create. + createBookmarkListener.onCreateBookmark(CreateBookmarkDialog.this); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when the `AlertDialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show the `AlertDialog` before we can call `setOnKeyListener()` below. + alertDialog.show(); + + // Allow the `enter` key on the keyboard to create the bookmark from `create_bookmark_name_edittext`. + EditText createBookmarkNameEditText = (EditText) alertDialog.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 view, int keyCode, KeyEvent event) { + // If the event is a key-down on the `enter` key, select the `PositiveButton` `Create`. + if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { + // Trigger `createBookmarkListener` and return the `DialogFragment` to the parent activity. + createBookmarkListener.onCreateBookmark(CreateBookmarkDialog.this); + // Manually dismiss the `AlertDialog`. + alertDialog.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) alertDialog.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" key, select the PositiveButton "Create". + if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) { + // Trigger `createBookmarkListener` and return the DialogFragment to the parent activity. + createBookmarkListener.onCreateBookmark(CreateBookmarkDialog.this); + // Manually dismiss the `AlertDialog`. + alertDialog.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 alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolder.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolder.java deleted file mode 100644 index 606915c5..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolder.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -import android.support.v7.app.AppCompatDialogFragment; -import android.view.KeyEvent; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.ImageView; - -import com.stoutner.privacybrowser.activities.MainWebView; -import com.stoutner.privacybrowser.R; - -public class CreateBookmarkFolder extends AppCompatDialogFragment { - // The public interface is used to send information back to the parent activity. - public interface CreateBookmarkFolderListener { - void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment); - } - - // `createBookmarkFolderListener` is used in `onAttach()` and `onCreateDialog`. - private CreateBookmarkFolderListener createBookmarkFolderListener; - - public void onAttach(Context context) { - super.onAttach(context); - - // Get a handle for `createBookmarkFolderListener` from `context`. - try { - createBookmarkFolderListener = (CreateBookmarkFolderListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement CreateBookmarkFolderListener."); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. - final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.create_folder); - // The parent view is `null` because it will be assigned by the `AlertDialog`. - dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.create_bookmark_folder_dialog, null)); - - // Set an `onClick()` listener for the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing. The `AlertDialog` will close automatically. - } - }); - - // Set an `onClick()` listener fo the positive button. - dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Return the `DialogFragment` to the parent activity on create. - createBookmarkFolderListener.onCreateBookmarkFolder(CreateBookmarkFolder.this); - } - }); - - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when the `Dialog` is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show the `AlertDialog` before we can call `setOnKeyListener()` below. - alertDialog.show(); - - // Allow the `enter` key on the keyboard to create the folder from `create_folder_name_edittext`. - EditText createFolderNameEditText = (EditText) alertDialog.findViewById(R.id.create_folder_name_edittext); - assert createFolderNameEditText != null; // Remove the warning below that `createFolderNameEditText` might be `null`. - createFolderNameEditText.setOnKeyListener(new View.OnKeyListener() { - public boolean onKey(View v, int keyCode, KeyEvent event) { - // If the event is a key-down on the `enter` key, select the `PositiveButton` `Create`. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // Trigger `createBookmarkFolderListener` and return the `DialogFragment` to the parent activity. - createBookmarkFolderListener.onCreateBookmarkFolder(CreateBookmarkFolder.this); - // Manually dismiss the `AlertDialog`. - alertDialog.dismiss(); - // Consume the event. - return true; - } else { // If any other key was pressed do not consume the event. - return false; - } - } - }); - - // Display the current favorite icon. - ImageView webPageIconImageView = (ImageView) alertDialog.findViewById(R.id.create_folder_web_page_icon); - assert webPageIconImageView != null; // Remove the warning that `webPageIconImageView` may be null. - webPageIconImageView.setImageBitmap(MainWebView.favoriteIcon); - - // `onCreateDialog()` requires the return of an `AlertDialog`. - return alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.java new file mode 100644 index 00000000..2b6986c1 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolderDialog.java @@ -0,0 +1,128 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +import android.support.v7.app.AppCompatDialogFragment; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ImageView; + +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +import com.stoutner.privacybrowser.R; + +public class CreateBookmarkFolderDialog extends AppCompatDialogFragment { + // The public interface is used to send information back to the parent activity. + public interface CreateBookmarkFolderListener { + void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment); + } + + // `createBookmarkFolderListener` is used in `onAttach()` and `onCreateDialog`. + private CreateBookmarkFolderListener createBookmarkFolderListener; + + public void onAttach(Context context) { + super.onAttach(context); + + // Get a handle for `createBookmarkFolderListener` from `context`. + try { + createBookmarkFolderListener = (CreateBookmarkFolderListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement CreateBookmarkFolderListener."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. + final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.create_folder); + // The parent view is `null` because it will be assigned by the `AlertDialog`. + dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.create_bookmark_folder_dialog, null)); + + // Set an `onClick()` listener for the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing. The `AlertDialog` will close automatically. + } + }); + + // Set an `onClick()` listener fo the positive button. + dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Return the `DialogFragment` to the parent activity on create. + createBookmarkFolderListener.onCreateBookmarkFolder(CreateBookmarkFolderDialog.this); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when the `Dialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show the `AlertDialog` before we can call `setOnKeyListener()` below. + alertDialog.show(); + + // Allow the `enter` key on the keyboard to create the folder from `create_folder_name_edittext`. + EditText createFolderNameEditText = (EditText) alertDialog.findViewById(R.id.create_folder_name_edittext); + assert createFolderNameEditText != null; // Remove the warning below that `createFolderNameEditText` might be `null`. + createFolderNameEditText.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + // If the event is a key-down on the `enter` key, select the `PositiveButton` `Create`. + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // Trigger `createBookmarkFolderListener` and return the `DialogFragment` to the parent activity. + createBookmarkFolderListener.onCreateBookmarkFolder(CreateBookmarkFolderDialog.this); + // Manually dismiss the `AlertDialog`. + alertDialog.dismiss(); + // Consume the event. + return true; + } else { // If any other key was pressed do not consume the event. + return false; + } + } + }); + + // Display the current favorite icon. + ImageView webPageIconImageView = (ImageView) alertDialog.findViewById(R.id.create_folder_web_page_icon); + assert webPageIconImageView != null; // Remove the warning that `webPageIconImageView` may be null. + webPageIconImageView.setImageBitmap(MainWebViewActivity.favoriteIcon); + + // `onCreateDialog()` requires the return of an `AlertDialog`. + return alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcut.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcut.java deleted file mode 100644 index 50a3aef5..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcut.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2015-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -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; - -import com.stoutner.privacybrowser.activities.MainWebView; -import com.stoutner.privacybrowser.R; - -public class CreateHomeScreenShortcut extends AppCompatDialogFragment { - // The public interface is used to send information back to the parent activity. - public interface CreateHomeScreenSchortcutListener { - void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment); - } - - //`createHomeScreenShortcutListener` is used in `onAttach()` and `onCreateDialog()`. - private CreateHomeScreenSchortcutListener createHomeScreenShortcutListener; - - // Check to make sure that the parent activity implements the listener. - public void onAttach(Context context) { - super.onAttach(context); - try { - createHomeScreenShortcutListener = (CreateHomeScreenSchortcutListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement CreateHomeScreenShortcutListener."); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get the activity's layout inflater. - LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - - // Create a drawable version of the favorite icon. - Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebView.favoriteIcon); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.create_shortcut); - dialogBuilder.setIcon(favoriteIconDrawable); - // The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null)); - - // Set an `onClick` listener on the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing if `Cancel` is clicked. - } - }); - - // Set an `onClick` listener on the positive button. - dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - createHomeScreenShortcutListener.onCreateHomeScreenShortcut(CreateHomeScreenShortcut.this); - } - }); - - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when the Dialog is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show `alertDialog` before we can call `setOnKeyListener()` below. - alertDialog.show(); - - // Allow the "enter" key on the keyboard to create the shortcut. - EditText shortcutNameEditText = (EditText) alertDialog.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 on the "enter" button, select the PositiveButton "Create". - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // Trigger the create listener. - createHomeScreenShortcutListener.onCreateHomeScreenShortcut(CreateHomeScreenShortcut.this); - - // Manually dismiss `alertDialog`. - alertDialog.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 alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java new file mode 100644 index 00000000..e5722bf8 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcutDialog.java @@ -0,0 +1,132 @@ +/* + * Copyright 2015-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +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; + +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +import com.stoutner.privacybrowser.R; + +public class CreateHomeScreenShortcutDialog extends AppCompatDialogFragment { + // The public interface is used to send information back to the parent activity. + public interface CreateHomeScreenSchortcutListener { + void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment); + } + + //`createHomeScreenShortcutListener` is used in `onAttach()` and `onCreateDialog()`. + private CreateHomeScreenSchortcutListener createHomeScreenShortcutListener; + + // Check to make sure that the parent activity implements the listener. + public void onAttach(Context context) { + super.onAttach(context); + try { + createHomeScreenShortcutListener = (CreateHomeScreenSchortcutListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement CreateHomeScreenShortcutListener."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Create a drawable version of the favorite icon. + Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIcon); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.create_shortcut); + dialogBuilder.setIcon(favoriteIconDrawable); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.create_home_screen_shortcut_dialog, null)); + + // Set an `onClick` listener on the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing if `Cancel` is clicked. + } + }); + + // Set an `onClick` listener on the positive button. + dialogBuilder.setPositiveButton(R.string.create, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + createHomeScreenShortcutListener.onCreateHomeScreenShortcut(CreateHomeScreenShortcutDialog.this); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when the Dialog is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show `alertDialog` before we can call `setOnKeyListener()` below. + alertDialog.show(); + + // Allow the "enter" key on the keyboard to create the shortcut. + EditText shortcutNameEditText = (EditText) alertDialog.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 on the "enter" button, select the PositiveButton "Create". + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // Trigger the create listener. + createHomeScreenShortcutListener.onCreateHomeScreenShortcut(CreateHomeScreenShortcutDialog.this); + + // Manually dismiss `alertDialog`. + alertDialog.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 alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFile.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFile.java deleted file mode 100644 index e4c2156f..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFile.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -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; -import android.widget.TextView; - -import com.stoutner.privacybrowser.R; - -import java.util.Locale; - -public class DownloadFile extends AppCompatDialogFragment { - - private String downloadUrl; - private String downloadFileName; - private String fileSize; - - public static DownloadFile fromUrl(String urlString, String contentDisposition, long contentLength) { - // Create `argumentsBundle`. - Bundle argumentsBundle = new Bundle(); - - String fileNameString; - - // Parse `filename` from `contentDisposition`. - if (contentDisposition.contains("filename=\"")) { // The file name is contained in a string surrounded by `""`. - fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=\"") + 10, contentDisposition.indexOf("\"", contentDisposition.indexOf("filename=\"") + 10)); - } else if (contentDisposition.contains("filename=") && ((contentDisposition.indexOf(";", contentDisposition.indexOf("filename=") + 9)) > 0 )) { // The file name is contained in a string beginning with `filename=` and ending with `;`. - fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9, contentDisposition.indexOf(";", contentDisposition.indexOf("filename=") + 9)); - } else if (contentDisposition.contains("filename=")) { // The file name is contained in a string beginning with `filename=` and proceeding to the end of `contentDisposition`. - fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9, contentDisposition.length()); - } else { // `contentDisposition` does not contain the filename, so use the last path segment of the URL. - Uri downloadUri = Uri.parse(urlString); - fileNameString = downloadUri.getLastPathSegment(); - } - - // Store the variables in the `Bundle`. - argumentsBundle.putString("URL", urlString); - argumentsBundle.putString("File_Name", fileNameString); - argumentsBundle.putLong("File_Size", contentLength); - - // Add `argumentsBundle` to this instance of `DownloadFile`. - DownloadFile thisDownloadFileDialog = new DownloadFile(); - thisDownloadFileDialog.setArguments(argumentsBundle); - return thisDownloadFileDialog; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Store the strings in the local class variables. - downloadUrl = getArguments().getString("URL"); - downloadFileName = getArguments().getString("File_Name"); - - // Get the `File_Size`. - long fileSizeLong = getArguments().getLong("File_Size"); - - // Convert `fileSizeLong` to a String. - if (fileSizeLong == -1) { // We don't know the file size. - fileSize = getString(R.string.unknown_size); - } else { // Convert `fileSize` to MB and store it in `fileSizeString`. `%.3g` displays the three most significant digits. - fileSize = String.format(Locale.getDefault(), "%.3g", (float) fileSizeLong / 1048576) + " MB"; - } - } - - // The public interface is used to send information back to the parent activity. - public interface DownloadFileListener { - void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl); - } - - // `downloadFileListener` is used in `onAttach()` and `onCreateDialog()`. - private DownloadFileListener downloadFileListener; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - - // Check to make sure the parent activity implements the listener. - try { - downloadFileListener = (DownloadFileListener) context; - } catch (ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement DownloadFileListener."); - } - } - - @Override - @NonNull - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get the activity's layout inflater. - LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.lightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - - // Set the title. - dialogBuilder.setTitle(R.string.save_as); - - // Set the view. The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.download_file_dialog, null)); - - // Set an `onClick()` listener on the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing if `Cancel` is clicked. The `Dialog` will automatically close. - } - }); - - // Set an `onClick()` listener on the positive button - dialogBuilder.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // trigger `onDownloadFile()` and return the `DialogFragment` and the download URL to the parent activity. - downloadFileListener.onDownloadFile(DownloadFile.this, downloadUrl); - } - }); - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when `alertDialog` is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show `alertDialog` before we can modify the contents. - alertDialog.show(); - - // Set the text for `downloadFileSizeTextView`. - TextView downloadFileSizeTextView = (TextView) alertDialog.findViewById(R.id.download_file_size); - assert downloadFileSizeTextView != null; // Remove the warning on the following line that `downloadFileSizeTextView` might be `null`. - downloadFileSizeTextView.setText(fileSize); - - // Set the text for `downloadFileNameTextView`. - EditText downloadFileNameTextView = (EditText) alertDialog.findViewById(R.id.download_file_name); - assert downloadFileNameTextView != null; // Remove the warning on the following line that `downloadFileNameTextView` might be `null`. - downloadFileNameTextView.setText(downloadFileName); - - // Allow the `enter` key on the keyboard to save the file from `downloadFileNameTextView`. - downloadFileNameTextView.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey (View v, int keyCode, KeyEvent event) { - // If the event is an `ACTION_DOWN` on the `enter` key, initiate the download. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // trigger `onDownloadFile()` and return the `DialogFragment` and the URL to the parent activity. - downloadFileListener.onDownloadFile(DownloadFile.this, downloadUrl); - // Manually dismiss `alertDialog`. - alertDialog.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 alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFileDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFileDialog.java new file mode 100644 index 00000000..9218c38e --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFileDialog.java @@ -0,0 +1,194 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +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; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; + +import java.util.Locale; + +public class DownloadFileDialog extends AppCompatDialogFragment { + + private String downloadUrl; + private String downloadFileName; + private String fileSize; + + public static DownloadFileDialog fromUrl(String urlString, String contentDisposition, long contentLength) { + // Create `argumentsBundle`. + Bundle argumentsBundle = new Bundle(); + + String fileNameString; + + // Parse `filename` from `contentDisposition`. + if (contentDisposition.contains("filename=\"")) { // The file name is contained in a string surrounded by `""`. + fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=\"") + 10, contentDisposition.indexOf("\"", contentDisposition.indexOf("filename=\"") + 10)); + } else if (contentDisposition.contains("filename=") && ((contentDisposition.indexOf(";", contentDisposition.indexOf("filename=") + 9)) > 0 )) { // The file name is contained in a string beginning with `filename=` and ending with `;`. + fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9, contentDisposition.indexOf(";", contentDisposition.indexOf("filename=") + 9)); + } else if (contentDisposition.contains("filename=")) { // The file name is contained in a string beginning with `filename=` and proceeding to the end of `contentDisposition`. + fileNameString = contentDisposition.substring(contentDisposition.indexOf("filename=") + 9, contentDisposition.length()); + } else { // `contentDisposition` does not contain the filename, so use the last path segment of the URL. + Uri downloadUri = Uri.parse(urlString); + fileNameString = downloadUri.getLastPathSegment(); + } + + // Store the variables in the `Bundle`. + argumentsBundle.putString("URL", urlString); + argumentsBundle.putString("File_Name", fileNameString); + argumentsBundle.putLong("File_Size", contentLength); + + // Add `argumentsBundle` to this instance of `DownloadFileDialog`. + DownloadFileDialog thisDownloadFileDialog = new DownloadFileDialog(); + thisDownloadFileDialog.setArguments(argumentsBundle); + return thisDownloadFileDialog; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Store the strings in the local class variables. + downloadUrl = getArguments().getString("URL"); + downloadFileName = getArguments().getString("File_Name"); + + // Get the `File_Size`. + long fileSizeLong = getArguments().getLong("File_Size"); + + // Convert `fileSizeLong` to a String. + if (fileSizeLong == -1) { // We don't know the file size. + fileSize = getString(R.string.unknown_size); + } else { // Convert `fileSize` to MB and store it in `fileSizeString`. `%.3g` displays the three most significant digits. + fileSize = String.format(Locale.getDefault(), "%.3g", (float) fileSizeLong / 1048576) + " MB"; + } + } + + // The public interface is used to send information back to the parent activity. + public interface DownloadFileListener { + void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl); + } + + // `downloadFileListener` is used in `onAttach()` and `onCreateDialog()`. + private DownloadFileListener downloadFileListener; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + // Check to make sure the parent activity implements the listener. + try { + downloadFileListener = (DownloadFileListener) context; + } catch (ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement DownloadFileListener."); + } + } + + @Override + @NonNull + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.lightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + + // Set the title. + dialogBuilder.setTitle(R.string.save_as); + + // Set the view. The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.download_file_dialog, null)); + + // Set an `onClick()` listener on the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing if `Cancel` is clicked. The `Dialog` will automatically close. + } + }); + + // Set an `onClick()` listener on the positive button + dialogBuilder.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // trigger `onDownloadFile()` and return the `DialogFragment` and the download URL to the parent activity. + downloadFileListener.onDownloadFile(DownloadFileDialog.this, downloadUrl); + } + }); + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when `alertDialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show `alertDialog` before we can modify the contents. + alertDialog.show(); + + // Set the text for `downloadFileSizeTextView`. + TextView downloadFileSizeTextView = (TextView) alertDialog.findViewById(R.id.download_file_size); + assert downloadFileSizeTextView != null; // Remove the warning on the following line that `downloadFileSizeTextView` might be `null`. + downloadFileSizeTextView.setText(fileSize); + + // Set the text for `downloadFileNameTextView`. + EditText downloadFileNameTextView = (EditText) alertDialog.findViewById(R.id.download_file_name); + assert downloadFileNameTextView != null; // Remove the warning on the following line that `downloadFileNameTextView` might be `null`. + downloadFileNameTextView.setText(downloadFileName); + + // Allow the `enter` key on the keyboard to save the file from `downloadFileNameTextView`. + downloadFileNameTextView.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey (View v, int keyCode, KeyEvent event) { + // If the event is an `ACTION_DOWN` on the `enter` key, initiate the download. + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // trigger `onDownloadFile()` and return the `DialogFragment` and the URL to the parent activity. + downloadFileListener.onDownloadFile(DownloadFileDialog.this, downloadUrl); + // Manually dismiss `alertDialog`. + alertDialog.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 alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImage.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImage.java deleted file mode 100644 index 8de68987..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImage.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. -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; - -import com.stoutner.privacybrowser.R; - -// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -public class DownloadImage extends AppCompatDialogFragment { - - private String imageUrl; - private String imageFileName; - - public static DownloadImage imageUrl(String imageUrlString) { - // Create `argumentsBundle`. - Bundle argumentsBundle = new Bundle(); - - String imageNameString; - - Uri imageUri = Uri.parse(imageUrlString); - imageNameString = imageUri.getLastPathSegment(); - - // Store the variables in the `Bundle`. - argumentsBundle.putString("URL", imageUrlString); - argumentsBundle.putString("Image_Name", imageNameString); - - // Add `argumentsBundle` to this instance of `DownloadFile`. - DownloadImage thisDownloadFileDialog = new DownloadImage(); - thisDownloadFileDialog.setArguments(argumentsBundle); - return thisDownloadFileDialog; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Store the strings in the local class variables. - imageUrl = getArguments().getString("URL"); - imageFileName = getArguments().getString("Image_Name"); - } - - // The public interface is used to send information back to the parent activity. - public interface DownloadImageListener { - void onDownloadImage(AppCompatDialogFragment dialogFragment, String downloadUrl); - } - - // `downloadImageListener` is used in `onAttach()` and `onCreateDialog()`. - private DownloadImageListener downloadImageListener; - - // Check to make sure tha the parent activity implements the listener. - @Override - public void onAttach(Context context) { - super.onAttach(context); - try { - downloadImageListener = (DownloadImageListener) context; - } catch (ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement DownloadImageListener."); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get the activity's layout inflater. - LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.lightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.save_image_as); - // The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.download_image_dialog, null)); - - // Set an `onClick()` listener on the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing if `Cancel` is clicked. - } - }); - - // Set an `onClick()` listener on the positive button - dialogBuilder.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // trigger `onDownloadFile()` and return the `DialogFragment` and the download URL to the parent activity. - downloadImageListener.onDownloadImage(DownloadImage.this, imageUrl); - } - }); - - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when `alertDialog` is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show `alertDialog` before we can modify the contents. - alertDialog.show(); - - // Set the text for `downloadImageNameTextView`. - EditText downloadImageNameTextView = (EditText) alertDialog.findViewById(R.id.download_image_name); - assert downloadImageNameTextView != null; // Remove the warning on the following line that `downloadImageNameTextView` might be `null`. - downloadImageNameTextView.setText(imageFileName); - - // Allow the `enter` key on the keyboard to save the file from `downloadImageNameTextView`. - downloadImageNameTextView.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey (View v, int keyCode, KeyEvent event) { - // If the event is an `ACTION_DOWN` on the `enter` key, initiate the download. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // trigger `onDownloadImage()` and return the `DialogFragment` and the URL to the parent activity. - downloadImageListener.onDownloadImage(DownloadImage.this, imageUrl); - // Manually dismiss `alertDialog`. - alertDialog.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 alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImageDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImageDialog.java new file mode 100644 index 00000000..6251e5e2 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImageDialog.java @@ -0,0 +1,165 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. +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; + +import com.stoutner.privacybrowser.R; + +// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +public class DownloadImageDialog extends AppCompatDialogFragment { + + private String imageUrl; + private String imageFileName; + + public static DownloadImageDialog imageUrl(String imageUrlString) { + // Create `argumentsBundle`. + Bundle argumentsBundle = new Bundle(); + + String imageNameString; + + Uri imageUri = Uri.parse(imageUrlString); + imageNameString = imageUri.getLastPathSegment(); + + // Store the variables in the `Bundle`. + argumentsBundle.putString("URL", imageUrlString); + argumentsBundle.putString("Image_Name", imageNameString); + + // Add `argumentsBundle` to this instance of `DownloadFileDialog`. + DownloadImageDialog thisDownloadFileDialog = new DownloadImageDialog(); + thisDownloadFileDialog.setArguments(argumentsBundle); + return thisDownloadFileDialog; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Store the strings in the local class variables. + imageUrl = getArguments().getString("URL"); + imageFileName = getArguments().getString("Image_Name"); + } + + // The public interface is used to send information back to the parent activity. + public interface DownloadImageListener { + void onDownloadImage(AppCompatDialogFragment dialogFragment, String downloadUrl); + } + + // `downloadImageListener` is used in `onAttach()` and `onCreateDialog()`. + private DownloadImageListener downloadImageListener; + + // Check to make sure tha the parent activity implements the listener. + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + downloadImageListener = (DownloadImageListener) context; + } catch (ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement DownloadImageListener."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.lightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.save_image_as); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.download_image_dialog, null)); + + // Set an `onClick()` listener on the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing if `Cancel` is clicked. + } + }); + + // Set an `onClick()` listener on the positive button + dialogBuilder.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // trigger `onDownloadFile()` and return the `DialogFragment` and the download URL to the parent activity. + downloadImageListener.onDownloadImage(DownloadImageDialog.this, imageUrl); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when `alertDialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show `alertDialog` before we can modify the contents. + alertDialog.show(); + + // Set the text for `downloadImageNameTextView`. + EditText downloadImageNameTextView = (EditText) alertDialog.findViewById(R.id.download_image_name); + assert downloadImageNameTextView != null; // Remove the warning on the following line that `downloadImageNameTextView` might be `null`. + downloadImageNameTextView.setText(imageFileName); + + // Allow the `enter` key on the keyboard to save the file from `downloadImageNameTextView`. + downloadImageNameTextView.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey (View v, int keyCode, KeyEvent event) { + // If the event is an `ACTION_DOWN` on the `enter` key, initiate the download. + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // trigger `onDownloadImage()` and return the `DialogFragment` and the URL to the parent activity. + downloadImageListener.onDownloadImage(DownloadImageDialog.this, imageUrl); + // Manually dismiss `alertDialog`. + alertDialog.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 alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmark.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmark.java deleted file mode 100644 index b502ca88..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmark.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -import android.support.v7.app.AppCompatDialogFragment; -import android.view.KeyEvent; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.ImageView; - -import com.stoutner.privacybrowser.activities.Bookmarks; -import com.stoutner.privacybrowser.activities.MainWebView; -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; - -public class EditBookmark extends AppCompatDialogFragment { - // The public interface is used to send information back to the parent activity. - public interface EditBookmarkListener { - void onSaveEditBookmark(AppCompatDialogFragment dialogFragment); - } - - // `editBookmarkListener` is used in `onAttach()` and `onCreateDialog()` - private EditBookmarkListener editBookmarkListener; - - public void onAttach(Context context) { - super.onAttach(context); - - // Get a handle for `EditBookmarkListener` from `context`. - try { - editBookmarkListener = (EditBookmarkListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement EditBookmarkListener."); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get a long array with the the databaseId of the selected bookmark and convert it to an `int`. - long[] selectedBookmarkLongArray = Bookmarks.checkedItemIds; - int selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; - - // Get a `Cursor` with the specified bookmark and move it to the first position. - Cursor bookmarkCursor = Bookmarks.bookmarksDatabaseHelper.getBookmarkCursor(selectedBookmarkDatabaseId); - bookmarkCursor.moveToFirst(); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.edit_bookmark); - // The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.edit_bookmark_dialog, null)); - - // Set an `onClick()` listener for the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing. The `AlertDialog` will close automatically. - } - }); - - // Set the `onClick()` listener fo the positive button. - dialogBuilder.setPositiveButton(R.string.save, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Return the `DialogFragment` to the parent activity on save. - editBookmarkListener.onSaveEditBookmark(EditBookmark.this); - } - }); - - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when `alertDialog` is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show the `AlertDialog` before we can modify items in the layout. - alertDialog.show(); - - // Get the current favorite icon byte array from the `Cursor`. - byte[] currentIconByteArray = bookmarkCursor.getBlob(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); - // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. - Bitmap currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.length); - // Display `currentIconBitmap` in `edit_bookmark_current_icon`. - ImageView currentIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_bookmark_current_icon); - assert currentIconImageView != null; // Remove the warning below that `currentIconImageView` might be null; - currentIconImageView.setImageBitmap(currentIconBitmap); - - // Get a `Bitmap` of the favorite icon from `MainWebView` and display it in `edit_bookmark_web_page_favorite_icon`. - ImageView newFavoriteIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_bookmark_web_page_favorite_icon); - assert newFavoriteIconImageView != null; // Remove the warning below that `newFavoriteIcon` might be null. - newFavoriteIconImageView.setImageBitmap(MainWebView.favoriteIcon); - - // Load the text for `edit_bookmark_name_edittext`. - EditText bookmarkNameEditText = (EditText) alertDialog.findViewById(R.id.edit_bookmark_name_edittext); - assert bookmarkNameEditText != null; // Remove the warning below that `bookmarkNameEditText` might be null. - bookmarkNameEditText.setText(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))); - - // Allow the `enter` key on the keyboard to save the bookmark from `edit_bookmark_name_edittext`. - bookmarkNameEditText.setOnKeyListener(new View.OnKeyListener() { - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - // If the event is an `ACTION_DOWN` on the `enter` key, save the bookmark. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // Trigger `onSaveEditBookmark()` and return the `DialogFragment` to the parent activity. - editBookmarkListener.onSaveEditBookmark(EditBookmark.this); - // Manually dismiss `alertDialog`. - alertDialog.dismiss(); - // Consume the event. - return true; - } else { // If any other key was pressed, do not consume the event. - return false; - } - } - }); - - // Load the text for `edit_bookmark_url_edittext`. - EditText bookmarkUrlEditText = (EditText) alertDialog.findViewById(R.id.edit_bookmark_url_edittext); - assert bookmarkUrlEditText != null;// Remove the warning below that `bookmarkUrlEditText` might be null. - bookmarkUrlEditText.setText(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL))); - - // Allow the "enter" key on the keyboard to save the bookmark from `edit_bookmark_url_edittext`. - bookmarkUrlEditText.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 `Save`. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // Trigger `editBookmarkListener` and return the DialogFragment to the parent activity. - editBookmarkListener.onSaveEditBookmark(EditBookmark.this); - // Manually dismiss the `AlertDialog`. - alertDialog.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 alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java new file mode 100644 index 00000000..a2db731b --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkDialog.java @@ -0,0 +1,176 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +import android.support.v7.app.AppCompatDialogFragment; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ImageView; + +import com.stoutner.privacybrowser.activities.BookmarksActivity; +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; + +public class EditBookmarkDialog extends AppCompatDialogFragment { + // The public interface is used to send information back to the parent activity. + public interface EditBookmarkListener { + void onSaveEditBookmark(AppCompatDialogFragment dialogFragment); + } + + // `editBookmarkListener` is used in `onAttach()` and `onCreateDialog()` + private EditBookmarkListener editBookmarkListener; + + public void onAttach(Context context) { + super.onAttach(context); + + // Get a handle for `EditBookmarkListener` from `context`. + try { + editBookmarkListener = (EditBookmarkListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement EditBookmarkListener."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get a long array with the the databaseId of the selected bookmark and convert it to an `int`. + long[] selectedBookmarkLongArray = BookmarksActivity.checkedItemIds; + int selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; + + // Get a `Cursor` with the specified bookmark and move it to the first position. + Cursor bookmarkCursor = BookmarksActivity.bookmarksDatabaseHelper.getBookmarkCursor(selectedBookmarkDatabaseId); + bookmarkCursor.moveToFirst(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.edit_bookmark); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.edit_bookmark_dialog, null)); + + // Set an `onClick()` listener for the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing. The `AlertDialog` will close automatically. + } + }); + + // Set the `onClick()` listener fo the positive button. + dialogBuilder.setPositiveButton(R.string.save, new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Return the `DialogFragment` to the parent activity on save. + editBookmarkListener.onSaveEditBookmark(EditBookmarkDialog.this); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when `alertDialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show the `AlertDialog` before we can modify items in the layout. + alertDialog.show(); + + // Get the current favorite icon byte array from the `Cursor`. + byte[] currentIconByteArray = bookmarkCursor.getBlob(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. + Bitmap currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.length); + // Display `currentIconBitmap` in `edit_bookmark_current_icon`. + ImageView currentIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_bookmark_current_icon); + assert currentIconImageView != null; // Remove the warning below that `currentIconImageView` might be null; + currentIconImageView.setImageBitmap(currentIconBitmap); + + // Get a `Bitmap` of the favorite icon from `MainWebViewActivity` and display it in `edit_bookmark_web_page_favorite_icon`. + ImageView newFavoriteIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_bookmark_web_page_favorite_icon); + assert newFavoriteIconImageView != null; // Remove the warning below that `newFavoriteIcon` might be null. + newFavoriteIconImageView.setImageBitmap(MainWebViewActivity.favoriteIcon); + + // Load the text for `edit_bookmark_name_edittext`. + EditText bookmarkNameEditText = (EditText) alertDialog.findViewById(R.id.edit_bookmark_name_edittext); + assert bookmarkNameEditText != null; // Remove the warning below that `bookmarkNameEditText` might be null. + bookmarkNameEditText.setText(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))); + + // Allow the `enter` key on the keyboard to save the bookmark from `edit_bookmark_name_edittext`. + bookmarkNameEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + // If the event is an `ACTION_DOWN` on the `enter` key, save the bookmark. + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // Trigger `onSaveEditBookmark()` and return the `DialogFragment` to the parent activity. + editBookmarkListener.onSaveEditBookmark(EditBookmarkDialog.this); + // Manually dismiss `alertDialog`. + alertDialog.dismiss(); + // Consume the event. + return true; + } else { // If any other key was pressed, do not consume the event. + return false; + } + } + }); + + // Load the text for `edit_bookmark_url_edittext`. + EditText bookmarkUrlEditText = (EditText) alertDialog.findViewById(R.id.edit_bookmark_url_edittext); + assert bookmarkUrlEditText != null;// Remove the warning below that `bookmarkUrlEditText` might be null. + bookmarkUrlEditText.setText(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL))); + + // Allow the "enter" key on the keyboard to save the bookmark from `edit_bookmark_url_edittext`. + bookmarkUrlEditText.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 `Save`. + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // Trigger `editBookmarkListener` and return the DialogFragment to the parent activity. + editBookmarkListener.onSaveEditBookmark(EditBookmarkDialog.this); + // Manually dismiss the `AlertDialog`. + alertDialog.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 alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolder.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolder.java deleted file mode 100644 index 77726ebe..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolder.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2016 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -import android.support.v7.app.AppCompatDialogFragment; -import android.view.KeyEvent; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; -import android.widget.ImageView; - -import com.stoutner.privacybrowser.activities.MainWebView; -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.activities.Bookmarks; -import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; - -public class EditBookmarkFolder extends AppCompatDialogFragment { - // The public interface is used to send information back to the parent activity. - public interface EditBookmarkFolderListener { - void onSaveEditBookmarkFolder(AppCompatDialogFragment dialogFragment); - } - - // `editFolderListener` is used in `onAttach()` and `onCreateDialog`. - private EditBookmarkFolderListener editBookmarkFolderListener; - - public void onAttach(Context context) { - super.onAttach(context); - - // Get a handle for `EditFolderListener` from `parentActivity`. - try { - editBookmarkFolderListener = (EditBookmarkFolderListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement EditBookmarkFolderListener."); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get a long array with the the databaseId of the selected bookmark and convert it to an `int`. - long[] selectedBookmarkLongArray = Bookmarks.checkedItemIds; - int selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; - - // Get a `Cursor` with the specified bookmark and move it to the first position. - Cursor bookmarkCursor = Bookmarks.bookmarksDatabaseHelper.getBookmarkCursor(selectedBookmarkDatabaseId); - bookmarkCursor.moveToFirst(); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.edit_folder); - // The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.edit_bookmark_folder_dialog, null)); - - // Set an `onClick()` listener for the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing. The `AlertDialog` will close automatically. - } - }); - - // Set the `onClick()` listener fo the positive button. - dialogBuilder.setPositiveButton(R.string.save, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Return the `DialogFragment` to the parent activity on save. - editBookmarkFolderListener.onSaveEditBookmarkFolder(EditBookmarkFolder.this); - } - }); - - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. - assert alertDialog.getWindow() != null; - - // Show the keyboard when the `Dialog` is displayed on the screen. - alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); - - // We need to show the `AlertDialog` before we can modify items in the layout. - alertDialog.show(); - - // Get the current favorite icon byte array from the `Cursor`. - byte[] currentIconByteArray = bookmarkCursor.getBlob(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); - // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. - Bitmap currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.length); - // Display `currentIconBitmap` in `edit_folder_current_icon`. - ImageView currentIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_folder_current_icon); - assert currentIconImageView != null; // Remove the warning below that `currentIconImageView` might be null. - currentIconImageView.setImageBitmap(currentIconBitmap); - - // Get a `Bitmap` of the favorite icon from `MainWebView` and display it in `edit_folder_web_page_favorite_icon`. - ImageView webPageFavoriteIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_folder_web_page_favorite_icon); - assert webPageFavoriteIconImageView != null; // Remove the warning below that `webPageFavoriteIcon` might be null. - webPageFavoriteIconImageView.setImageBitmap(MainWebView.favoriteIcon); - - // Load the text for `edit_folder_name_edittext`. - EditText folderNameEditText = (EditText) alertDialog.findViewById(R.id.edit_folder_name_edittext); - assert folderNameEditText != null; // Remove the warning below that `bookmarkNameEditText` might be null. - folderNameEditText.setText(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))); - - // Allow the `enter` key on the keyboard to save the bookmark from `edit_bookmark_name_edittext`. - folderNameEditText.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 `Save`. - if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { - // Trigger `editBookmarkListener` and return the DialogFragment to the parent activity. - editBookmarkFolderListener.onSaveEditBookmarkFolder(EditBookmarkFolder.this); - // Manually dismiss the `AlertDialog`. - alertDialog.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 alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.java new file mode 100644 index 00000000..a2a71152 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolderDialog.java @@ -0,0 +1,153 @@ +/* + * Copyright 2016 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +import android.support.v7.app.AppCompatDialogFragment; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ImageView; + +import com.stoutner.privacybrowser.activities.BookmarksActivity; +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; + +public class EditBookmarkFolderDialog extends AppCompatDialogFragment { + // The public interface is used to send information back to the parent activity. + public interface EditBookmarkFolderListener { + void onSaveEditBookmarkFolder(AppCompatDialogFragment dialogFragment); + } + + // `editFolderListener` is used in `onAttach()` and `onCreateDialog`. + private EditBookmarkFolderListener editBookmarkFolderListener; + + public void onAttach(Context context) { + super.onAttach(context); + + // Get a handle for `EditFolderListener` from `parentActivity`. + try { + editBookmarkFolderListener = (EditBookmarkFolderListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement EditBookmarkFolderListener."); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get a long array with the the databaseId of the selected bookmark and convert it to an `int`. + long[] selectedBookmarkLongArray = BookmarksActivity.checkedItemIds; + int selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; + + // Get a `Cursor` with the specified bookmark and move it to the first position. + Cursor bookmarkCursor = BookmarksActivity.bookmarksDatabaseHelper.getBookmarkCursor(selectedBookmarkDatabaseId); + bookmarkCursor.moveToFirst(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.edit_folder); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.edit_bookmark_folder_dialog, null)); + + // Set an `onClick()` listener for the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing. The `AlertDialog` will close automatically. + } + }); + + // Set the `onClick()` listener fo the positive button. + dialogBuilder.setPositiveButton(R.string.save, new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Return the `DialogFragment` to the parent activity on save. + editBookmarkFolderListener.onSaveEditBookmarkFolder(EditBookmarkFolderDialog.this); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // Remove the warning below that `setSoftInputMode` might produce `java.lang.NullPointerException`. + assert alertDialog.getWindow() != null; + + // Show the keyboard when the `Dialog` is displayed on the screen. + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + + // We need to show the `AlertDialog` before we can modify items in the layout. + alertDialog.show(); + + // Get the current favorite icon byte array from the `Cursor`. + byte[] currentIconByteArray = bookmarkCursor.getBlob(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. + Bitmap currentIconBitmap = BitmapFactory.decodeByteArray(currentIconByteArray, 0, currentIconByteArray.length); + // Display `currentIconBitmap` in `edit_folder_current_icon`. + ImageView currentIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_folder_current_icon); + assert currentIconImageView != null; // Remove the warning below that `currentIconImageView` might be null. + currentIconImageView.setImageBitmap(currentIconBitmap); + + // Get a `Bitmap` of the favorite icon from `MainWebViewActivity` and display it in `edit_folder_web_page_favorite_icon`. + ImageView webPageFavoriteIconImageView = (ImageView) alertDialog.findViewById(R.id.edit_folder_web_page_favorite_icon); + assert webPageFavoriteIconImageView != null; // Remove the warning below that `webPageFavoriteIcon` might be null. + webPageFavoriteIconImageView.setImageBitmap(MainWebViewActivity.favoriteIcon); + + // Load the text for `edit_folder_name_edittext`. + EditText folderNameEditText = (EditText) alertDialog.findViewById(R.id.edit_folder_name_edittext); + assert folderNameEditText != null; // Remove the warning below that `bookmarkNameEditText` might be null. + folderNameEditText.setText(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME))); + + // Allow the `enter` key on the keyboard to save the bookmark from `edit_bookmark_name_edittext`. + folderNameEditText.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 `Save`. + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // Trigger `editBookmarkListener` and return the DialogFragment to the parent activity. + editBookmarkFolderListener.onSaveEditBookmarkFolder(EditBookmarkFolderDialog.this); + // Manually dismiss the `AlertDialog`. + alertDialog.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 alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolder.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolder.java deleted file mode 100644 index 217281c1..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolder.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.MatrixCursor; -import android.database.MergeCursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -import android.support.v7.app.AppCompatDialogFragment; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.activities.Bookmarks; -import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; - -import java.io.ByteArrayOutputStream; - -public class MoveToFolder extends AppCompatDialogFragment { - // The public interface is used to send information back to the parent activity. - public interface MoveToFolderListener { - void onMoveToFolder(AppCompatDialogFragment dialogFragment); - } - - // `moveToFolderListener` is used in `onAttach()` and `onCreateDialog`. - private MoveToFolderListener moveToFolderListener; - - public void onAttach(Context context) { - super.onAttach(context); - - // Get a handle for `MoveToFolderListener` from `parentActivity`. - try { - moveToFolderListener = (MoveToFolderListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement EditBookmarkFolderListener."); - } - } - - // `exceptFolders` is used in `onCreateDialog()` and `addSubfoldersToExceptFolders()`. - private String exceptFolders; - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.move_to_folder); - // The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_dialog, null)); - - // Set an `onClick()` listener for the negative button. - dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing. The `AlertDialog` will close automatically. - } - }); - - // Set the `onClick()` listener fo the positive button. - dialogBuilder.setPositiveButton(R.string.move, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Return the `DialogFragment` to the parent activity on save. - moveToFolderListener.onMoveToFolder(MoveToFolder.this); - } - }); - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // We need to show the `AlertDialog` before we can modify items in the layout. - alertDialog.show(); - - // Initialize the `Cursor` and `CursorAdapter` variables. - Cursor foldersCursor; - CursorAdapter foldersCursorAdapter; - - // Check to see if we are in the `Home Folder`. - if (Bookmarks.currentFolder.isEmpty()) { // Don't display `Home Folder` at the top of the `ListView`. - // Initialize `exceptFolders`. - exceptFolders = ""; - - // If a folder is selected, add it and all children to the list of folders not to display. - long[] selectedBookmarksLongArray = Bookmarks.checkedItemIds; - for (long databaseIdLong : selectedBookmarksLongArray) { - // Get `databaseIdInt` for each selected bookmark. - int databaseIdInt = (int) databaseIdLong; - - // If `databaseIdInt` is a folder. - if (Bookmarks.bookmarksDatabaseHelper.isFolder(databaseIdInt)) { - // Get the name of the selected folder. - String folderName = Bookmarks.bookmarksDatabaseHelper.getFolderName(databaseIdInt); - - if (exceptFolders.isEmpty()){ - // Add the selected folder to the list of folders not to display. - exceptFolders = DatabaseUtils.sqlEscapeString(folderName); - } else { - // Add the selected folder to the end of the list of folders not to display. - exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName); - } - - // Add the selected folder's subfolders to the list of folders not to display. - addSubfoldersToExceptFolders(folderName); - } - } - - // Get a `Cursor` containing the folders to display. - foldersCursor = Bookmarks.bookmarksDatabaseHelper.getFoldersCursorExcept(exceptFolders); - - // Setup `foldersCursorAdaptor` with `this` context. `false` disables autoRequery. - foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Get the folder icon from `cursor`. - byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); - // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. - Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length); - // Display `folderIconBitmap` in `move_to_folder_icon`. - ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon); - assert folderIconImageView != null; // Remove the warning below that `currentIconImageView` might be null; - folderIconImageView.setImageBitmap(folderIconBitmap); - - // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`. - String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview); - folderNameTextView.setText(folderName); - } - }; - } else { // Display `Home Folder` at the top of the `ListView`. - // Get the home folder icon drawable and convert it to a `Bitmap`. `this` specifies the current context. - Drawable homeFolderIconDrawable = ContextCompat.getDrawable(getActivity().getApplicationContext(), R.drawable.folder_gray_bitmap); - BitmapDrawable homeFolderIconBitmapDrawable = (BitmapDrawable) homeFolderIconDrawable; - Bitmap homeFolderIconBitmap = homeFolderIconBitmapDrawable.getBitmap(); - // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). - ByteArrayOutputStream homeFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); - homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream); - byte[] homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray(); - - // Setup a `MatrixCursor` for the `Home Folder`. - String[] homeFolderMatrixCursorColumnNames = {BookmarksDatabaseHelper._ID, BookmarksDatabaseHelper.BOOKMARK_NAME, BookmarksDatabaseHelper.FAVORITE_ICON}; - MatrixCursor homeFolderMatrixCursor = new MatrixCursor(homeFolderMatrixCursorColumnNames); - homeFolderMatrixCursor.addRow(new Object[]{0, getString(R.string.home_folder), homeFolderIconByteArray}); - - // Add the parent folder to the list of folders not to display. - exceptFolders = DatabaseUtils.sqlEscapeString(Bookmarks.currentFolder); - - // If a folder is selected, add it and all children to the list of folders not to display. - long[] selectedBookmarksLongArray = Bookmarks.checkedItemIds; - for (long databaseIdLong : selectedBookmarksLongArray) { - // Get `databaseIdInt` for each selected bookmark. - int databaseIdInt = (int) databaseIdLong; - - // If `databaseIdInt` is a folder. - if (Bookmarks.bookmarksDatabaseHelper.isFolder(databaseIdInt)) { - // Get the name of the selected folder. - String folderName = Bookmarks.bookmarksDatabaseHelper.getFolderName(databaseIdInt); - - // Add the selected folder to the end of the list of folders not to display. - exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName); - - // Add the selected folder's subfolders to the list of folders not to display. - addSubfoldersToExceptFolders(folderName); - } - } - - // Get a `foldersCursor`. - foldersCursor = Bookmarks.bookmarksDatabaseHelper.getFoldersCursorExcept(exceptFolders); - - // Combine `homeFolderMatrixCursor` and `foldersCursor`. - MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{homeFolderMatrixCursor, foldersCursor}); - - // Setup `foldersCursorAdaptor` with `this` context. `false` disables autoRequery. - foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersMergeCursor, false) { - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - // Inflate the individual item layout. `false` does not attach it to the root. - return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Get the folder icon from `cursor`. - byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); - // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. - Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length); - // Display `folderIconBitmap` in `move_to_folder_icon`. - ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon); - assert folderIconImageView != null; // Remove the warning below that `currentIconImageView` might be null; - folderIconImageView.setImageBitmap(folderIconBitmap); - - // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`. - String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview); - folderNameTextView.setText(folderName); - } - }; - } - - // Display the ListView - ListView foldersListView = (ListView) alertDialog.findViewById(R.id.move_to_folder_listview); - assert foldersListView != null; // Remove the warning below that `foldersListView` might be null. - foldersListView.setAdapter(foldersCursorAdapter); - - // `onCreateDialog` requires the return of an `AlertDialog`. - return alertDialog; - } - - private void addSubfoldersToExceptFolders(String folderName) { - // Get a `Cursor` will all the immediate subfolders. - Cursor subfoldersCursor = Bookmarks.bookmarksDatabaseHelper.getSubfoldersCursor(folderName); - - for (int i = 0; i < subfoldersCursor.getCount(); i++) { - // Move `subfolderCursor` to the current item. - subfoldersCursor.moveToPosition(i); - - // Get the name of the subfolder. - String subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); - - // Run the same tasks for any subfolders of the subfolder. - addSubfoldersToExceptFolders(subfolderName); - - // Add the subfolder to `exceptFolders`. - subfolderName = DatabaseUtils.sqlEscapeString(subfolderName); - exceptFolders = exceptFolders + "," + subfolderName; - } - - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.java new file mode 100644 index 00000000..bf863444 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolderDialog.java @@ -0,0 +1,270 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.MatrixCursor; +import android.database.MergeCursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +import android.support.v7.app.AppCompatDialogFragment; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.activities.BookmarksActivity; +import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper; + +import java.io.ByteArrayOutputStream; + +public class MoveToFolderDialog extends AppCompatDialogFragment { + // The public interface is used to send information back to the parent activity. + public interface MoveToFolderListener { + void onMoveToFolder(AppCompatDialogFragment dialogFragment); + } + + // `moveToFolderListener` is used in `onAttach()` and `onCreateDialog`. + private MoveToFolderListener moveToFolderListener; + + public void onAttach(Context context) { + super.onAttach(context); + + // Get a handle for `MoveToFolderListener` from `parentActivity`. + try { + moveToFolderListener = (MoveToFolderListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement EditBookmarkFolderListener."); + } + } + + // `exceptFolders` is used in `onCreateDialog()` and `addSubfoldersToExceptFolders()`. + private String exceptFolders; + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use `AlertDialog.Builder` to create the `AlertDialog`. The style formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.move_to_folder); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_dialog, null)); + + // Set an `onClick()` listener for the negative button. + dialogBuilder.setNegativeButton(R.string.cancel, new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing. The `AlertDialog` will close automatically. + } + }); + + // Set the `onClick()` listener fo the positive button. + dialogBuilder.setPositiveButton(R.string.move, new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Return the `DialogFragment` to the parent activity on save. + moveToFolderListener.onMoveToFolder(MoveToFolderDialog.this); + } + }); + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // We need to show the `AlertDialog` before we can modify items in the layout. + alertDialog.show(); + + // Initialize the `Cursor` and `CursorAdapter` variables. + Cursor foldersCursor; + CursorAdapter foldersCursorAdapter; + + // Check to see if we are in the `Home Folder`. + if (BookmarksActivity.currentFolder.isEmpty()) { // Don't display `Home Folder` at the top of the `ListView`. + // Initialize `exceptFolders`. + exceptFolders = ""; + + // If a folder is selected, add it and all children to the list of folders not to display. + long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds; + for (long databaseIdLong : selectedBookmarksLongArray) { + // Get `databaseIdInt` for each selected bookmark. + int databaseIdInt = (int) databaseIdLong; + + // If `databaseIdInt` is a folder. + if (BookmarksActivity.bookmarksDatabaseHelper.isFolder(databaseIdInt)) { + // Get the name of the selected folder. + String folderName = BookmarksActivity.bookmarksDatabaseHelper.getFolderName(databaseIdInt); + + if (exceptFolders.isEmpty()){ + // Add the selected folder to the list of folders not to display. + exceptFolders = DatabaseUtils.sqlEscapeString(folderName); + } else { + // Add the selected folder to the end of the list of folders not to display. + exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName); + } + + // Add the selected folder's subfolders to the list of folders not to display. + addSubfoldersToExceptFolders(folderName); + } + } + + // Get a `Cursor` containing the folders to display. + foldersCursor = BookmarksActivity.bookmarksDatabaseHelper.getFoldersCursorExcept(exceptFolders); + + // Setup `foldersCursorAdaptor` with `this` context. `false` disables autoRequery. + foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersCursor, false) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // Inflate the individual item layout. `false` does not attach it to the root. + return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Get the folder icon from `cursor`. + byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. + Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length); + // Display `folderIconBitmap` in `move_to_folder_icon`. + ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon); + assert folderIconImageView != null; // Remove the warning below that `currentIconImageView` might be null; + folderIconImageView.setImageBitmap(folderIconBitmap); + + // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`. + String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview); + folderNameTextView.setText(folderName); + } + }; + } else { // Display `Home Folder` at the top of the `ListView`. + // Get the home folder icon drawable and convert it to a `Bitmap`. `this` specifies the current context. + Drawable homeFolderIconDrawable = ContextCompat.getDrawable(getActivity().getApplicationContext(), R.drawable.folder_gray_bitmap); + BitmapDrawable homeFolderIconBitmapDrawable = (BitmapDrawable) homeFolderIconDrawable; + Bitmap homeFolderIconBitmap = homeFolderIconBitmapDrawable.getBitmap(); + // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG). + ByteArrayOutputStream homeFolderIconByteArrayOutputStream = new ByteArrayOutputStream(); + homeFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, homeFolderIconByteArrayOutputStream); + byte[] homeFolderIconByteArray = homeFolderIconByteArrayOutputStream.toByteArray(); + + // Setup a `MatrixCursor` for the `Home Folder`. + String[] homeFolderMatrixCursorColumnNames = {BookmarksDatabaseHelper._ID, BookmarksDatabaseHelper.BOOKMARK_NAME, BookmarksDatabaseHelper.FAVORITE_ICON}; + MatrixCursor homeFolderMatrixCursor = new MatrixCursor(homeFolderMatrixCursorColumnNames); + homeFolderMatrixCursor.addRow(new Object[]{0, getString(R.string.home_folder), homeFolderIconByteArray}); + + // Add the parent folder to the list of folders not to display. + exceptFolders = DatabaseUtils.sqlEscapeString(BookmarksActivity.currentFolder); + + // If a folder is selected, add it and all children to the list of folders not to display. + long[] selectedBookmarksLongArray = BookmarksActivity.checkedItemIds; + for (long databaseIdLong : selectedBookmarksLongArray) { + // Get `databaseIdInt` for each selected bookmark. + int databaseIdInt = (int) databaseIdLong; + + // If `databaseIdInt` is a folder. + if (BookmarksActivity.bookmarksDatabaseHelper.isFolder(databaseIdInt)) { + // Get the name of the selected folder. + String folderName = BookmarksActivity.bookmarksDatabaseHelper.getFolderName(databaseIdInt); + + // Add the selected folder to the end of the list of folders not to display. + exceptFolders = exceptFolders + "," + DatabaseUtils.sqlEscapeString(folderName); + + // Add the selected folder's subfolders to the list of folders not to display. + addSubfoldersToExceptFolders(folderName); + } + } + + // Get a `foldersCursor`. + foldersCursor = BookmarksActivity.bookmarksDatabaseHelper.getFoldersCursorExcept(exceptFolders); + + // Combine `homeFolderMatrixCursor` and `foldersCursor`. + MergeCursor foldersMergeCursor = new MergeCursor(new Cursor[]{homeFolderMatrixCursor, foldersCursor}); + + // Setup `foldersCursorAdaptor` with `this` context. `false` disables autoRequery. + foldersCursorAdapter = new CursorAdapter(alertDialog.getContext(), foldersMergeCursor, false) { + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // Inflate the individual item layout. `false` does not attach it to the root. + return getActivity().getLayoutInflater().inflate(R.layout.move_to_folder_item_linearlayout, parent, false); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + // Get the folder icon from `cursor`. + byte[] folderIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON)); + // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last. + Bitmap folderIconBitmap = BitmapFactory.decodeByteArray(folderIconByteArray, 0, folderIconByteArray.length); + // Display `folderIconBitmap` in `move_to_folder_icon`. + ImageView folderIconImageView = (ImageView) view.findViewById(R.id.move_to_folder_icon); + assert folderIconImageView != null; // Remove the warning below that `currentIconImageView` might be null; + folderIconImageView.setImageBitmap(folderIconBitmap); + + // Get the folder name from `cursor` and display it in `move_to_folder_name_textview`. + String folderName = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + TextView folderNameTextView = (TextView) view.findViewById(R.id.move_to_folder_name_textview); + folderNameTextView.setText(folderName); + } + }; + } + + // Display the ListView + ListView foldersListView = (ListView) alertDialog.findViewById(R.id.move_to_folder_listview); + assert foldersListView != null; // Remove the warning below that `foldersListView` might be null. + foldersListView.setAdapter(foldersCursorAdapter); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + } + + private void addSubfoldersToExceptFolders(String folderName) { + // Get a `Cursor` will all the immediate subfolders. + Cursor subfoldersCursor = BookmarksActivity.bookmarksDatabaseHelper.getSubfoldersCursor(folderName); + + for (int i = 0; i < subfoldersCursor.getCount(); i++) { + // Move `subfolderCursor` to the current item. + subfoldersCursor.moveToPosition(i); + + // Get the name of the subfolder. + String subfolderName = subfoldersCursor.getString(subfoldersCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME)); + + // Run the same tasks for any subfolders of the subfolder. + addSubfoldersToExceptFolders(subfolderName); + + // Add the subfolder to `exceptFolders`. + subfolderName = DatabaseUtils.sqlEscapeString(subfolderName); + exceptFolders = exceptFolders + "," + subfolderName; + } + + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateError.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateError.java deleted file mode 100644 index 917b3027..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateError.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.net.http.SslCertificate; -import android.net.http.SslError; -import android.os.Bundle; -import android.support.annotation.NonNull; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. -import android.support.v7.app.AppCompatDialogFragment; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.widget.TextView; - -import com.stoutner.privacybrowser.R; - -import java.text.DateFormat; -import java.util.Date; - -public class SslCertificateError extends AppCompatDialogFragment { - - private String primaryError; - private String urlWithError; - private String issuedToCName; - private String issuedToOName; - private String issuedToUName; - private String issuedByCName; - private String issuedByOName; - private String issuedByUName; - private String startDate; - private String endDate; - - public static SslCertificateError displayDialog(SslError error) { - // Get the various components of the SSL error message. - int primaryErrorIntForBundle = error.getPrimaryError(); - String urlWithErrorForBundle = error.getUrl(); - SslCertificate sslCertificate = error.getCertificate(); - String issuedToCNameForBundle = sslCertificate.getIssuedTo().getCName(); - String issuedToONameForBundle = sslCertificate.getIssuedTo().getOName(); - String issuedToUNameForBundle = sslCertificate.getIssuedTo().getUName(); - String issuedByCNameForBundle = sslCertificate.getIssuedBy().getCName(); - String issuedByONameForBundle = sslCertificate.getIssuedBy().getOName(); - String issuedByUNameForBundle = sslCertificate.getIssuedBy().getUName(); - Date startDateForBundle = sslCertificate.getValidNotBeforeDate(); - Date endDateForBundle = sslCertificate.getValidNotAfterDate(); - - // Store the SSL error message components in a `Bundle`. - Bundle argumentsBundle = new Bundle(); - argumentsBundle.putInt("PrimaryErrorInt", primaryErrorIntForBundle); - argumentsBundle.putString("UrlWithError", urlWithErrorForBundle); - argumentsBundle.putString("IssuedToCName", issuedToCNameForBundle); - argumentsBundle.putString("IssuedToOName", issuedToONameForBundle); - argumentsBundle.putString("IssuedToUName", issuedToUNameForBundle); - argumentsBundle.putString("IssuedByCName", issuedByCNameForBundle); - argumentsBundle.putString("IssuedByOName", issuedByONameForBundle); - argumentsBundle.putString("IssuedByUName", issuedByUNameForBundle); - argumentsBundle.putString("StartDate", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDateForBundle)); - argumentsBundle.putString("EndDate", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDateForBundle)); - - // Add `argumentsBundle` to this instance of `SslCertificateError`. - SslCertificateError thisSslCertificateErrorDialog = new SslCertificateError(); - thisSslCertificateErrorDialog.setArguments(argumentsBundle); - return thisSslCertificateErrorDialog; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Save the components of the SSL error message in class variables. - urlWithError = getArguments().getString("UrlWithError"); - issuedToCName = getArguments().getString("IssuedToCName"); - issuedToOName = getArguments().getString("IssuedToOName"); - issuedToUName = getArguments().getString("IssuedToUName"); - issuedByCName = getArguments().getString("IssuedByCName"); - issuedByOName = getArguments().getString("IssuedByOName"); - issuedByUName = getArguments().getString("IssuedByUName"); - startDate = getArguments().getString("StartDate"); - endDate = getArguments().getString("EndDate"); - - // Get the appropriate string for `primaryError. - int primaryErrorInt = getArguments().getInt("PrimaryErrorInt"); - switch (primaryErrorInt) { - case SslError.SSL_NOTYETVALID: - primaryError = getString(R.string.future_certificate); - break; - - case SslError.SSL_EXPIRED: - primaryError = getString(R.string.expired_certificate); - break; - - case SslError.SSL_IDMISMATCH: - primaryError = getString(R.string.cn_mismatch); - break; - - case SslError.SSL_UNTRUSTED: - primaryError = getString(R.string.untrusted); - break; - - case SslError.SSL_DATE_INVALID: - primaryError = getString(R.string.invalid_date); - break; - - case SslError.SSL_INVALID: - primaryError = getString(R.string.invalid_certificate); - break; - } - } - - // The public interface is used to send information back to the parent activity. - public interface SslCertificateErrorListener { - void onSslErrorCancel(); - - void onSslErrorProceed(); - } - - // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`. - private SslCertificateErrorListener sslCertificateErrorListener; - - // Check to make sure that the parent activity implements the listener. - public void onAttach(Context context) { - super.onAttach(context); - - try { - sslCertificateErrorListener = (SslCertificateErrorListener) context; - } catch(ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement SslCertificateErrorListener"); - } - } - - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - @Override - @NonNull - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get the activity's layout inflater. - LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setTitle(R.string.ssl_certificate_error); - // The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null)); - - // Set an `onClick` listener on the negative button. `null` doesn't do anything extra when the button is pressed. The `Dialog` will automatically close. - dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - sslCertificateErrorListener.onSslErrorCancel(); - } - }); - - // Set an `onClick` listener on the positive button. - dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - sslCertificateErrorListener.onSslErrorProceed(); - } - }); - - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - AlertDialog alertDialog = dialogBuilder.create(); - - // We have to show the `AlertDialog` before we can modify the content. - alertDialog.show(); - - // Get handles for the `TextViews` - TextView primaryErrorTextView = (TextView) alertDialog.findViewById(R.id.primary_error); - TextView urlTextView = (TextView) alertDialog.findViewById(R.id.url_error_dialog); - TextView issuedToCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_cname_error_dialog); - TextView issuedToONameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_oname_error_dialog); - TextView issuedToUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_uname_error_dialog); - TextView issuedByCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_cname_error_dialog); - TextView issuedByONameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_oname_error_dialog); - TextView issuedByUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_uname_error_dialog); - TextView startDateTextView = (TextView) alertDialog.findViewById(R.id.start_date_error_dialog); - TextView endDateTextView = (TextView) alertDialog.findViewById(R.id.end_date_error_dialog); - - // Setup the common strings. - String urlLabel = getString(R.string.url_label) + " "; - String cNameLabel = getString(R.string.common_name) + " "; - String oNameLabel = getString(R.string.organization) + " "; - String uNameLabel = getString(R.string.organizational_unit) + " "; - String startDateLabel = getString(R.string.start_date) + " "; - String endDateLabel = getString(R.string.end_date) + " "; - - // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. - SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithError); - SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName); - SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName); - SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName); - SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCName); - SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByOName); - SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUName); - SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate); - SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder((endDateLabel + endDate)); - - // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. - @SuppressWarnings("deprecation") ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); - - // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - urlStringBuilder.setSpan(blueColorSpan, urlLabel.length(), urlStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - - // Display the strings. - primaryErrorTextView.setText(primaryError); - urlTextView.setText(urlStringBuilder); - issuedToCNameTextView.setText(issuedToCNameStringBuilder); - issuedToONameTextView.setText(issuedToONameStringBuilder); - issuedToUNameTextView.setText(issuedToUNameStringBuilder); - issuedByCNameTextView.setText(issuedByCNameStringBuilder); - issuedByONameTextView.setText(issuedByONameStringBuilder); - issuedByUNameTextView.setText(issuedByUNameStringBuilder); - startDateTextView.setText(startDateStringBuilder); - endDateTextView.setText(endDateStringBuilder); - - // `onCreateDialog` requires the return of an `AlertDialog`. - return alertDialog; - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java new file mode 100644 index 00000000..0416d160 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateErrorDialog.java @@ -0,0 +1,253 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.http.SslCertificate; +import android.net.http.SslError; +import android.os.Bundle; +import android.support.annotation.NonNull; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. +import android.support.v7.app.AppCompatDialogFragment; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; + +import java.text.DateFormat; +import java.util.Date; + +public class SslCertificateErrorDialog extends AppCompatDialogFragment { + + private String primaryError; + private String urlWithError; + private String issuedToCName; + private String issuedToOName; + private String issuedToUName; + private String issuedByCName; + private String issuedByOName; + private String issuedByUName; + private String startDate; + private String endDate; + + public static SslCertificateErrorDialog displayDialog(SslError error) { + // Get the various components of the SSL error message. + int primaryErrorIntForBundle = error.getPrimaryError(); + String urlWithErrorForBundle = error.getUrl(); + SslCertificate sslCertificate = error.getCertificate(); + String issuedToCNameForBundle = sslCertificate.getIssuedTo().getCName(); + String issuedToONameForBundle = sslCertificate.getIssuedTo().getOName(); + String issuedToUNameForBundle = sslCertificate.getIssuedTo().getUName(); + String issuedByCNameForBundle = sslCertificate.getIssuedBy().getCName(); + String issuedByONameForBundle = sslCertificate.getIssuedBy().getOName(); + String issuedByUNameForBundle = sslCertificate.getIssuedBy().getUName(); + Date startDateForBundle = sslCertificate.getValidNotBeforeDate(); + Date endDateForBundle = sslCertificate.getValidNotAfterDate(); + + // Store the SSL error message components in a `Bundle`. + Bundle argumentsBundle = new Bundle(); + argumentsBundle.putInt("PrimaryErrorInt", primaryErrorIntForBundle); + argumentsBundle.putString("UrlWithError", urlWithErrorForBundle); + argumentsBundle.putString("IssuedToCName", issuedToCNameForBundle); + argumentsBundle.putString("IssuedToOName", issuedToONameForBundle); + argumentsBundle.putString("IssuedToUName", issuedToUNameForBundle); + argumentsBundle.putString("IssuedByCName", issuedByCNameForBundle); + argumentsBundle.putString("IssuedByOName", issuedByONameForBundle); + argumentsBundle.putString("IssuedByUName", issuedByUNameForBundle); + argumentsBundle.putString("StartDate", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDateForBundle)); + argumentsBundle.putString("EndDate", DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDateForBundle)); + + // Add `argumentsBundle` to this instance of `SslCertificateErrorDialog`. + SslCertificateErrorDialog thisSslCertificateErrorDialog = new SslCertificateErrorDialog(); + thisSslCertificateErrorDialog.setArguments(argumentsBundle); + return thisSslCertificateErrorDialog; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Save the components of the SSL error message in class variables. + urlWithError = getArguments().getString("UrlWithError"); + issuedToCName = getArguments().getString("IssuedToCName"); + issuedToOName = getArguments().getString("IssuedToOName"); + issuedToUName = getArguments().getString("IssuedToUName"); + issuedByCName = getArguments().getString("IssuedByCName"); + issuedByOName = getArguments().getString("IssuedByOName"); + issuedByUName = getArguments().getString("IssuedByUName"); + startDate = getArguments().getString("StartDate"); + endDate = getArguments().getString("EndDate"); + + // Get the appropriate string for `primaryError. + int primaryErrorInt = getArguments().getInt("PrimaryErrorInt"); + switch (primaryErrorInt) { + case SslError.SSL_NOTYETVALID: + primaryError = getString(R.string.future_certificate); + break; + + case SslError.SSL_EXPIRED: + primaryError = getString(R.string.expired_certificate); + break; + + case SslError.SSL_IDMISMATCH: + primaryError = getString(R.string.cn_mismatch); + break; + + case SslError.SSL_UNTRUSTED: + primaryError = getString(R.string.untrusted); + break; + + case SslError.SSL_DATE_INVALID: + primaryError = getString(R.string.invalid_date); + break; + + case SslError.SSL_INVALID: + primaryError = getString(R.string.invalid_certificate); + break; + } + } + + // The public interface is used to send information back to the parent activity. + public interface SslCertificateErrorListener { + void onSslErrorCancel(); + + void onSslErrorProceed(); + } + + // `sslCertificateErrorListener` is used in `onAttach` and `onCreateDialog`. + private SslCertificateErrorListener sslCertificateErrorListener; + + // Check to make sure that the parent activity implements the listener. + public void onAttach(Context context) { + super.onAttach(context); + + try { + sslCertificateErrorListener = (SslCertificateErrorListener) context; + } catch(ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement SslCertificateErrorListener"); + } + } + + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + @Override + @NonNull + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setTitle(R.string.ssl_certificate_error); + // The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.ssl_certificate_error, null)); + + // Set an `onClick` listener on the negative button. `null` doesn't do anything extra when the button is pressed. The `Dialog` will automatically close. + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + sslCertificateErrorListener.onSslErrorCancel(); + } + }); + + // Set an `onClick` listener on the positive button. + dialogBuilder.setPositiveButton(R.string.proceed, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + sslCertificateErrorListener.onSslErrorProceed(); + } + }); + + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + AlertDialog alertDialog = dialogBuilder.create(); + + // We have to show the `AlertDialog` before we can modify the content. + alertDialog.show(); + + // Get handles for the `TextViews` + TextView primaryErrorTextView = (TextView) alertDialog.findViewById(R.id.primary_error); + TextView urlTextView = (TextView) alertDialog.findViewById(R.id.url_error_dialog); + TextView issuedToCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_cname_error_dialog); + TextView issuedToONameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_oname_error_dialog); + TextView issuedToUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_uname_error_dialog); + TextView issuedByCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_cname_error_dialog); + TextView issuedByONameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_oname_error_dialog); + TextView issuedByUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_uname_error_dialog); + TextView startDateTextView = (TextView) alertDialog.findViewById(R.id.start_date_error_dialog); + TextView endDateTextView = (TextView) alertDialog.findViewById(R.id.end_date_error_dialog); + + // Setup the common strings. + String urlLabel = getString(R.string.url_label) + " "; + String cNameLabel = getString(R.string.common_name) + " "; + String oNameLabel = getString(R.string.organization) + " "; + String uNameLabel = getString(R.string.organizational_unit) + " "; + String startDateLabel = getString(R.string.start_date) + " "; + String endDateLabel = getString(R.string.end_date) + " "; + + // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. + SpannableStringBuilder urlStringBuilder = new SpannableStringBuilder(urlLabel + urlWithError); + SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCName); + SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToOName); + SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUName); + SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCName); + SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByOName); + SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUName); + SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + startDate); + SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder((endDateLabel + endDate)); + + // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. + @SuppressWarnings("deprecation") ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); + + // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + urlStringBuilder.setSpan(blueColorSpan, urlLabel.length(), urlStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + + // Display the strings. + primaryErrorTextView.setText(primaryError); + urlTextView.setText(urlStringBuilder); + issuedToCNameTextView.setText(issuedToCNameStringBuilder); + issuedToONameTextView.setText(issuedToONameStringBuilder); + issuedToUNameTextView.setText(issuedToUNameStringBuilder); + issuedByCNameTextView.setText(issuedByCNameStringBuilder); + issuedByONameTextView.setText(issuedByONameStringBuilder); + issuedByUNameTextView.setText(issuedByUNameStringBuilder); + startDateTextView.setText(startDateStringBuilder); + endDateTextView.setText(endDateStringBuilder); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistory.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistory.java deleted file mode 100644 index a362b704..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistory.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. `android.support.v7.app.AlertDialog` also uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. -import android.support.v7.app.AppCompatDialogFragment; -import android.util.Base64; -import android.view.LayoutInflater; -import android.view.View; -import android.webkit.WebBackForwardList; -import android.widget.AdapterView; -import android.widget.ListView; - -import com.stoutner.privacybrowser.R; -import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter; -import com.stoutner.privacybrowser.definitions.History; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; - -public class UrlHistory extends AppCompatDialogFragment{ - - // `historyArrayList` and `currentPageId` pass information from `onCreate()` to `onCreateDialog()`. - private ArrayList historyArrayList = new ArrayList<>(); - private int currentPageId; - - public static UrlHistory loadBackForwardList(Context context, WebBackForwardList webBackForwardList) { - // Create `argumentsBundle`. - Bundle argumentsBundle = new Bundle(); - - // Store `currentPageIndex`. - int currentPageIndex = webBackForwardList.getCurrentIndex(); - - // Setup `urlArrayList` and `iconArrayList`. - ArrayList urlArrayList = new ArrayList<>(); - ArrayList iconBase64StringArrayList = new ArrayList<>(); - - // Get the default favorite icon `Drawable`. - Drawable defaultFavoriteIconDrawable = ContextCompat.getDrawable(context, R.drawable.world); - - // Convert `defaultFavoriteIconDrawable` to a `BitmapDrawable`. - BitmapDrawable defaultFavoriteIconBitmapDrawable = (BitmapDrawable) defaultFavoriteIconDrawable; - - // Extract a `Bitmap` from `defaultFavoriteIconBitmapDrawable`. - Bitmap defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.getBitmap(); - - // Populate `urlArrayList` and `iconArrayList` from `webBackForwardList`. - for (int i=0; i < webBackForwardList.getSize(); i++) { - // Store the URL. - urlArrayList.add(webBackForwardList.getItemAtIndex(i).getUrl()); - - // Create a variable to store the icon `Bitmap`. - Bitmap iconBitmap; - - // Store the icon `Bitmap`. - if (webBackForwardList.getItemAtIndex(i).getFavicon() == null) { - // If `webBackForwardList` does not have a favorite icon, use Privacy Browser's default world icon. - iconBitmap = defaultFavoriteIcon; - } else { // Get the icon from `webBackForwardList`. - iconBitmap = webBackForwardList.getItemAtIndex(i).getFavicon(); - } - - // Create a `ByteArrayOutputStream`. - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - // Convert the favorite icon `Bitmap` to a `ByteArrayOutputStream`. `100` is the compression quality, which is ignored by `PNG`. - iconBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); - - // Convert the favorite icon `ByteArrayOutputStream` to a `byte[]`. - byte[] byteArray = byteArrayOutputStream.toByteArray(); - - // Encode the favorite icon `byte[]` as a Base64 `String`. - String iconBase64String = Base64.encodeToString(byteArray, Base64.DEFAULT); - - // Store the favorite icon Base64 `String` in `iconBase64StringArrayList`. - iconBase64StringArrayList.add(iconBase64String); - } - - // Store the variables in the `Bundle`. - argumentsBundle.putInt("Current_Page", currentPageIndex); - argumentsBundle.putStringArrayList("URL_History", urlArrayList); - argumentsBundle.putStringArrayList("Favorite_Icons", iconBase64StringArrayList); - - // Add `argumentsBundle` to this instance of `UrlHistory`. - UrlHistory thisUrlHistoryDialog = new UrlHistory(); - thisUrlHistoryDialog.setArguments(argumentsBundle); - return thisUrlHistoryDialog; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Get the `ArrayLists` from the `Arguments`. - ArrayList urlStringArrayList = getArguments().getStringArrayList("URL_History"); - ArrayList favoriteIconBase64StringArrayList = getArguments().getStringArrayList("Favorite_Icons"); - - // Remove the lint warning below that the `ArrayLists` might be `null`. - assert urlStringArrayList != null; - assert favoriteIconBase64StringArrayList != null; - - // Populate `historyArrayList`. We go down from `urlStringArrayList.size()` so that the newest entries are at the top. `-1` is needed because `historyArrayList` is zero-based. - for (int i=urlStringArrayList.size() -1; i >= 0; i--) { - // Decode the favorite icon Base64 `String` to a `byte[]`. - byte[] favoriteIconByteArray = Base64.decode(favoriteIconBase64StringArrayList.get(i), Base64.DEFAULT); - - // Convert the favorite icon `byte[]` to a `Bitmap`. `0` is the starting offset. - Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length); - - // Store the favorite icon and the URL in `historyEntry`. - History historyEntry = new History(favoriteIconBitmap, urlStringArrayList.get(i)); - - // Add this history entry to `historyArrayList`. - historyArrayList.add(historyEntry); - } - - // Get the original current page ID. - int originalCurrentPageId = getArguments().getInt("Current_Page"); - - // Subtract `originalCurrentPageId` from the array size because we reversed the order of the array so that the newest entries are at the top. `-1` is needed because the array is zero-based. - currentPageId = urlStringArrayList.size() - 1 - originalCurrentPageId; - } - - // The public interface is used to send information back to the parent activity. - public interface UrlHistoryListener { - // Send back the number of steps to move forward or back. - void onUrlHistoryEntrySelected(int moveBackOrForwardSteps); - - // Clear the history. - void onClearHistory(); - } - - // `urlHistoryListener` is used in `onAttach()` and `onCreateDialog()`. - private UrlHistoryListener urlHistoryListener; - - @Override - public void onAttach(Context context) { - super.onAttach(context); - - // Check to make sure tha the parent activity implements the listener. - try { - urlHistoryListener = (UrlHistoryListener) context; - } catch (ClassCastException exception) { - throw new ClassCastException(context.toString() + " must implement UrlHistoryListener."); - } - } - - @Override - @NonNull - // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. - @SuppressLint("InflateParams") - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get the activity's layout inflater. - LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.lightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - - // Set the title. - dialogBuilder.setTitle(R.string.history); - - // Set the view. The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.url_history_dialog, null)); - - // Set an `onClick()` listener on the negative button. - dialogBuilder.setNegativeButton(R.string.clear_history, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Clear the history. - urlHistoryListener.onClearHistory(); - } - }); - - // Set an `onClick()` listener on the positive button. - dialogBuilder.setPositiveButton(R.string.close, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing if `Close` is clicked. The `Dialog` will automatically close. - } - }); - - // Create an `AlertDialog` from the `AlertDialog.Builder`. - final AlertDialog alertDialog = dialogBuilder.create(); - - // We need to show `alertDialog` before we can modify the contents. - alertDialog.show(); - - // Instantiate a `HistoryArrayAdapter`. - final HistoryArrayAdapter historyArrayAdapter = new HistoryArrayAdapter(getContext(), historyArrayList, currentPageId); - - // Get a handle for `listView`. - ListView listView = (ListView) alertDialog.findViewById(R.id.history_listview); - - // Remove the warning below that `listView` might be `null`. - assert listView != null; - - // Set the adapter on `listView`. - listView.setAdapter(historyArrayAdapter); - - // Listen for clicks on entries in `listView`. - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - // Convert the `long` `id` to an `int`. - int itemId = (int) id; - - // Only enable the click if it is not on the `currentPageId`. - if (itemId != currentPageId) { - // Get the history entry for this `itemId`. - History historyEntry = historyArrayAdapter.getItem(itemId); - - // Remove the lint warning below that `historyEntry` might be `null`. - assert historyEntry != null; - - // Send the history entry URL to be loaded in `mainWebView`. - urlHistoryListener.onUrlHistoryEntrySelected(currentPageId - itemId); - - // Dismiss the `Dialog`. - alertDialog.dismiss(); - } - } - }); - - // `onCreateDialog` requires the return of an `AlertDialog`. - return alertDialog; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.java new file mode 100644 index 00000000..4811ea2d --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistoryDialog.java @@ -0,0 +1,256 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <= 22. `android.support.v7.app.AlertDialog` also uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. +import android.support.v7.app.AppCompatDialogFragment; +import android.util.Base64; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.WebBackForwardList; +import android.widget.AdapterView; +import android.widget.ListView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.adapters.HistoryArrayAdapter; +import com.stoutner.privacybrowser.definitions.History; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; + +public class UrlHistoryDialog extends AppCompatDialogFragment{ + + // `historyArrayList` and `currentPageId` pass information from `onCreate()` to `onCreateDialog()`. + private ArrayList historyArrayList = new ArrayList<>(); + private int currentPageId; + + public static UrlHistoryDialog loadBackForwardList(Context context, WebBackForwardList webBackForwardList) { + // Create `argumentsBundle`. + Bundle argumentsBundle = new Bundle(); + + // Store `currentPageIndex`. + int currentPageIndex = webBackForwardList.getCurrentIndex(); + + // Setup `urlArrayList` and `iconArrayList`. + ArrayList urlArrayList = new ArrayList<>(); + ArrayList iconBase64StringArrayList = new ArrayList<>(); + + // Get the default favorite icon `Drawable`. + Drawable defaultFavoriteIconDrawable = ContextCompat.getDrawable(context, R.drawable.world); + + // Convert `defaultFavoriteIconDrawable` to a `BitmapDrawable`. + BitmapDrawable defaultFavoriteIconBitmapDrawable = (BitmapDrawable) defaultFavoriteIconDrawable; + + // Extract a `Bitmap` from `defaultFavoriteIconBitmapDrawable`. + Bitmap defaultFavoriteIcon = defaultFavoriteIconBitmapDrawable.getBitmap(); + + // Populate `urlArrayList` and `iconArrayList` from `webBackForwardList`. + for (int i=0; i < webBackForwardList.getSize(); i++) { + // Store the URL. + urlArrayList.add(webBackForwardList.getItemAtIndex(i).getUrl()); + + // Create a variable to store the icon `Bitmap`. + Bitmap iconBitmap; + + // Store the icon `Bitmap`. + if (webBackForwardList.getItemAtIndex(i).getFavicon() == null) { + // If `webBackForwardList` does not have a favorite icon, use Privacy Browser's default world icon. + iconBitmap = defaultFavoriteIcon; + } else { // Get the icon from `webBackForwardList`. + iconBitmap = webBackForwardList.getItemAtIndex(i).getFavicon(); + } + + // Create a `ByteArrayOutputStream`. + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + // Convert the favorite icon `Bitmap` to a `ByteArrayOutputStream`. `100` is the compression quality, which is ignored by `PNG`. + iconBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + + // Convert the favorite icon `ByteArrayOutputStream` to a `byte[]`. + byte[] byteArray = byteArrayOutputStream.toByteArray(); + + // Encode the favorite icon `byte[]` as a Base64 `String`. + String iconBase64String = Base64.encodeToString(byteArray, Base64.DEFAULT); + + // Store the favorite icon Base64 `String` in `iconBase64StringArrayList`. + iconBase64StringArrayList.add(iconBase64String); + } + + // Store the variables in the `Bundle`. + argumentsBundle.putInt("Current_Page", currentPageIndex); + argumentsBundle.putStringArrayList("URL_History", urlArrayList); + argumentsBundle.putStringArrayList("Favorite_Icons", iconBase64StringArrayList); + + // Add `argumentsBundle` to this instance of `UrlHistoryDialog`. + UrlHistoryDialog thisUrlHistoryDialog = new UrlHistoryDialog(); + thisUrlHistoryDialog.setArguments(argumentsBundle); + return thisUrlHistoryDialog; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Get the `ArrayLists` from the `Arguments`. + ArrayList urlStringArrayList = getArguments().getStringArrayList("URL_History"); + ArrayList favoriteIconBase64StringArrayList = getArguments().getStringArrayList("Favorite_Icons"); + + // Remove the lint warning below that the `ArrayLists` might be `null`. + assert urlStringArrayList != null; + assert favoriteIconBase64StringArrayList != null; + + // Populate `historyArrayList`. We go down from `urlStringArrayList.size()` so that the newest entries are at the top. `-1` is needed because `historyArrayList` is zero-based. + for (int i=urlStringArrayList.size() -1; i >= 0; i--) { + // Decode the favorite icon Base64 `String` to a `byte[]`. + byte[] favoriteIconByteArray = Base64.decode(favoriteIconBase64StringArrayList.get(i), Base64.DEFAULT); + + // Convert the favorite icon `byte[]` to a `Bitmap`. `0` is the starting offset. + Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length); + + // Store the favorite icon and the URL in `historyEntry`. + History historyEntry = new History(favoriteIconBitmap, urlStringArrayList.get(i)); + + // Add this history entry to `historyArrayList`. + historyArrayList.add(historyEntry); + } + + // Get the original current page ID. + int originalCurrentPageId = getArguments().getInt("Current_Page"); + + // Subtract `originalCurrentPageId` from the array size because we reversed the order of the array so that the newest entries are at the top. `-1` is needed because the array is zero-based. + currentPageId = urlStringArrayList.size() - 1 - originalCurrentPageId; + } + + // The public interface is used to send information back to the parent activity. + public interface UrlHistoryListener { + // Send back the number of steps to move forward or back. + void onUrlHistoryEntrySelected(int moveBackOrForwardSteps); + + // Clear the history. + void onClearHistory(); + } + + // `urlHistoryListener` is used in `onAttach()` and `onCreateDialog()`. + private UrlHistoryListener urlHistoryListener; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + + // Check to make sure tha the parent activity implements the listener. + try { + urlHistoryListener = (UrlHistoryListener) context; + } catch (ClassCastException exception) { + throw new ClassCastException(context.toString() + " must implement UrlHistoryListener."); + } + } + + @Override + @NonNull + // `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. + @SuppressLint("InflateParams") + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.lightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + + // Set the title. + dialogBuilder.setTitle(R.string.history); + + // Set the view. The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.url_history_dialog, null)); + + // Set an `onClick()` listener on the negative button. + dialogBuilder.setNegativeButton(R.string.clear_history, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Clear the history. + urlHistoryListener.onClearHistory(); + } + }); + + // Set an `onClick()` listener on the positive button. + dialogBuilder.setPositiveButton(R.string.close, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing if `Close` is clicked. The `Dialog` will automatically close. + } + }); + + // Create an `AlertDialog` from the `AlertDialog.Builder`. + final AlertDialog alertDialog = dialogBuilder.create(); + + // We need to show `alertDialog` before we can modify the contents. + alertDialog.show(); + + // Instantiate a `HistoryArrayAdapter`. + final HistoryArrayAdapter historyArrayAdapter = new HistoryArrayAdapter(getContext(), historyArrayList, currentPageId); + + // Get a handle for `listView`. + ListView listView = (ListView) alertDialog.findViewById(R.id.history_listview); + + // Remove the warning below that `listView` might be `null`. + assert listView != null; + + // Set the adapter on `listView`. + listView.setAdapter(historyArrayAdapter); + + // Listen for clicks on entries in `listView`. + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + // Convert the `long` `id` to an `int`. + int itemId = (int) id; + + // Only enable the click if it is not on the `currentPageId`. + if (itemId != currentPageId) { + // Get the history entry for this `itemId`. + History historyEntry = historyArrayAdapter.getItem(itemId); + + // Remove the lint warning below that `historyEntry` might be `null`. + assert historyEntry != null; + + // Send the history entry URL to be loaded in `mainWebView`. + urlHistoryListener.onUrlHistoryEntrySelected(currentPageId - itemId); + + // Dismiss the `Dialog`. + alertDialog.dismiss(); + } + } + }); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificate.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificate.java deleted file mode 100644 index 4b16ab1c..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificate.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2016-2017 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.dialogs; - -import android.annotation.SuppressLint; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.DialogFragment; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.net.http.SslCertificate; -import android.os.Bundle; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.widget.TextView; - -import com.stoutner.privacybrowser.activities.MainWebView; -import com.stoutner.privacybrowser.R; - -import java.text.DateFormat; -import java.util.Date; - -// `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. -@SuppressLint("InflateParams") -public class ViewSslCertificate extends DialogFragment { - public Dialog onCreateDialog(Bundle savedInstanceState) { - // Get the activity's layout inflater. - LayoutInflater layoutInflater = getActivity().getLayoutInflater(); - - // Create a drawable version of the favorite icon. - Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebView.favoriteIcon); - - // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); - dialogBuilder.setIcon(favoriteIconDrawable); - - // Set an `onClick` listener on the negative button. Using `null` closes the dialog without doing anything else. - dialogBuilder.setNegativeButton(R.string.close, null); - - // Check to see if the website is encrypted. - if (MainWebView.sslCertificate == null) { // The website is not encrypted. - // Set the title. - dialogBuilder.setTitle(R.string.unencrypted_website); - - // Set the Layout. The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website, null)); - - // Create an `AlertDialog` from the `AlertDialog.Builder` - final AlertDialog alertDialog = dialogBuilder.create(); - - // Show `alertDialog`. - alertDialog.show(); - - // `onCreateDialog` requires the return of an `AlertDialog`. - return alertDialog; - - } else { // Display the SSL certificate information - // Set the title. - dialogBuilder.setTitle(R.string.ssl_certificate); - - // Set the layout. The parent view is `null` because it will be assigned by `AlertDialog`. - dialogBuilder.setView(layoutInflater.inflate(R.layout.view_ssl_certificate, null)); - - // Create an `AlertDialog` from the `AlertDialog.Builder` - final AlertDialog alertDialog = dialogBuilder.create(); - - // We need to show the `AlertDialog` before we can modify items in the layout. - alertDialog.show(); - - // Get handles for the `TextViews`. - TextView issuedToCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_cname); - TextView issuedToONameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_oname); - TextView issuedToUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_uname); - TextView issuedByCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_cname); - TextView issuedByONameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_oname); - TextView issuedByUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_uname); - TextView startDateTextView = (TextView) alertDialog.findViewById(R.id.start_date); - TextView endDateTextView = (TextView) alertDialog.findViewById(R.id.end_date); - - // Setup the labels. - String cNameLabel = getString(R.string.common_name) + " "; - String oNameLabel = getString(R.string.organization) + " "; - String uNameLabel = getString(R.string.organizational_unit) + " "; - String startDateLabel = getString(R.string.start_date) + " "; - String endDateLabel = getString(R.string.end_date) + " "; - - // Get the SSL certificate. - SslCertificate sslCertificate = MainWebView.sslCertificate; - - // Get the strings from the SSL certificate. - String issuedToCNameString = sslCertificate.getIssuedTo().getCName(); - String issuedToONameString = sslCertificate.getIssuedTo().getOName(); - String issuedToUNameString = sslCertificate.getIssuedTo().getUName(); - String issuedByCNameString = sslCertificate.getIssuedBy().getCName(); - String issuedByONameString = sslCertificate.getIssuedBy().getOName(); - String issuedByUNameString = sslCertificate.getIssuedBy().getUName(); - Date startDate = sslCertificate.getValidNotBeforeDate(); - Date endDate = sslCertificate.getValidNotAfterDate(); - - // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. - SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCNameString); - SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToONameString); - SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUNameString); - SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCNameString); - SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByONameString); - SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUNameString); - SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate)); - SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate)); - - // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. - @SuppressWarnings("deprecation") ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); - - // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Display the strings. - issuedToCNameTextView.setText(issuedToCNameStringBuilder); - issuedToONameTextView.setText(issuedToONameStringBuilder); - issuedToUNameTextView.setText(issuedToUNameStringBuilder); - issuedByCNameTextView.setText(issuedByCNameStringBuilder); - issuedByONameTextView.setText(issuedByONameStringBuilder); - issuedByUNameTextView.setText(issuedByUNameStringBuilder); - startDateTextView.setText(startDateStringBuilder); - endDateTextView.setText(endDateStringBuilder); - - // `onCreateDialog` requires the return of an `AlertDialog`. - return alertDialog; - } - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java new file mode 100644 index 00000000..ccd546b2 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificateDialog.java @@ -0,0 +1,156 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.dialogs; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.http.SslCertificate; +import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.widget.TextView; + +import com.stoutner.privacybrowser.activities.MainWebViewActivity; +import com.stoutner.privacybrowser.R; + +import java.text.DateFormat; +import java.util.Date; + +// `@SuppressLing("InflateParams")` removes the warning about using `null` as the parent view group when inflating the `AlertDialog`. +@SuppressLint("InflateParams") +public class ViewSslCertificateDialog extends DialogFragment { + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Get the activity's layout inflater. + LayoutInflater layoutInflater = getActivity().getLayoutInflater(); + + // Create a drawable version of the favorite icon. + Drawable favoriteIconDrawable = new BitmapDrawable(getResources(), MainWebViewActivity.favoriteIcon); + + // Use `AlertDialog.Builder` to create the `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity(), R.style.LightAlertDialog); + dialogBuilder.setIcon(favoriteIconDrawable); + + // Set an `onClick` listener on the negative button. Using `null` closes the dialog without doing anything else. + dialogBuilder.setNegativeButton(R.string.close, null); + + // Check to see if the website is encrypted. + if (MainWebViewActivity.sslCertificate == null) { // The website is not encrypted. + // Set the title. + dialogBuilder.setTitle(R.string.unencrypted_website); + + // Set the Layout. The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.unencrypted_website, null)); + + // Create an `AlertDialog` from the `AlertDialog.Builder` + final AlertDialog alertDialog = dialogBuilder.create(); + + // Show `alertDialog`. + alertDialog.show(); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + + } else { // Display the SSL certificate information + // Set the title. + dialogBuilder.setTitle(R.string.ssl_certificate); + + // Set the layout. The parent view is `null` because it will be assigned by `AlertDialog`. + dialogBuilder.setView(layoutInflater.inflate(R.layout.view_ssl_certificate, null)); + + // Create an `AlertDialog` from the `AlertDialog.Builder` + final AlertDialog alertDialog = dialogBuilder.create(); + + // We need to show the `AlertDialog` before we can modify items in the layout. + alertDialog.show(); + + // Get handles for the `TextViews`. + TextView issuedToCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_cname); + TextView issuedToONameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_oname); + TextView issuedToUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_to_uname); + TextView issuedByCNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_cname); + TextView issuedByONameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_oname); + TextView issuedByUNameTextView = (TextView) alertDialog.findViewById(R.id.issued_by_uname); + TextView startDateTextView = (TextView) alertDialog.findViewById(R.id.start_date); + TextView endDateTextView = (TextView) alertDialog.findViewById(R.id.end_date); + + // Setup the labels. + String cNameLabel = getString(R.string.common_name) + " "; + String oNameLabel = getString(R.string.organization) + " "; + String uNameLabel = getString(R.string.organizational_unit) + " "; + String startDateLabel = getString(R.string.start_date) + " "; + String endDateLabel = getString(R.string.end_date) + " "; + + // Get the SSL certificate. + SslCertificate sslCertificate = MainWebViewActivity.sslCertificate; + + // Get the strings from the SSL certificate. + String issuedToCNameString = sslCertificate.getIssuedTo().getCName(); + String issuedToONameString = sslCertificate.getIssuedTo().getOName(); + String issuedToUNameString = sslCertificate.getIssuedTo().getUName(); + String issuedByCNameString = sslCertificate.getIssuedBy().getCName(); + String issuedByONameString = sslCertificate.getIssuedBy().getOName(); + String issuedByUNameString = sslCertificate.getIssuedBy().getUName(); + Date startDate = sslCertificate.getValidNotBeforeDate(); + Date endDate = sslCertificate.getValidNotAfterDate(); + + // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. + SpannableStringBuilder issuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedToCNameString); + SpannableStringBuilder issuedToONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedToONameString); + SpannableStringBuilder issuedToUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedToUNameString); + SpannableStringBuilder issuedByCNameStringBuilder = new SpannableStringBuilder(cNameLabel + issuedByCNameString); + SpannableStringBuilder issuedByONameStringBuilder = new SpannableStringBuilder(oNameLabel + issuedByONameString); + SpannableStringBuilder issuedByUNameStringBuilder = new SpannableStringBuilder(uNameLabel + issuedByUNameString); + SpannableStringBuilder startDateStringBuilder = new SpannableStringBuilder(startDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(startDate)); + SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder(endDateLabel + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG).format(endDate)); + + // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. + @SuppressWarnings("deprecation") ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); + + // Setup the spans to display the certificate information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + issuedToCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedToCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedToONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedToUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedToUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByCNameStringBuilder.setSpan(blueColorSpan, cNameLabel.length(), issuedByCNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByONameStringBuilder.setSpan(blueColorSpan, oNameLabel.length(), issuedByONameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + issuedByUNameStringBuilder.setSpan(blueColorSpan, uNameLabel.length(), issuedByUNameStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + startDateStringBuilder.setSpan(blueColorSpan, startDateLabel.length(), startDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + endDateStringBuilder.setSpan(blueColorSpan, endDateLabel.length(), endDateStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Display the strings. + issuedToCNameTextView.setText(issuedToCNameStringBuilder); + issuedToONameTextView.setText(issuedToONameStringBuilder); + issuedToUNameTextView.setText(issuedToUNameStringBuilder); + issuedByCNameTextView.setText(issuedByCNameStringBuilder); + issuedByONameTextView.setText(issuedByONameStringBuilder); + issuedByUNameTextView.setText(issuedByUNameStringBuilder); + startDateTextView.setText(startDateStringBuilder); + endDateTextView.setText(endDateStringBuilder); + + // `onCreateDialog` requires the return of an `AlertDialog`. + return alertDialog; + } + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTab.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTab.java deleted file mode 100644 index 57971307..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTab.java +++ /dev/null @@ -1,208 +0,0 @@ -/** - * Copyright 2016 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.fragments; - -import android.os.Build; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.ForegroundColorSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebView; -import android.widget.TextView; - -import com.stoutner.privacybrowser.BuildConfig; -import com.stoutner.privacybrowser.R; - -public class AboutTab extends Fragment { - private int tabNumber; - - // AboutTab.createTab stores the tab number in the bundle arguments so it can be referenced from onCreate(). - public static AboutTab createTab(int tab) { - Bundle thisTabArguments = new Bundle(); - thisTabArguments.putInt("Tab", tab); - - AboutTab thisTab = new AboutTab(); - thisTab.setArguments(thisTabArguments); - return thisTab; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Store the tab number in tabNumber. - tabNumber = getArguments().getInt("Tab"); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View tabLayout; - - // Load the tabs. Tab numbers start at 0. - if (tabNumber == 0) { // Load the about tab. - // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container. - // The fragment will take care of attaching the root automatically. - tabLayout = inflater.inflate(R.layout.about_tab_version, container, false); - - // Get handles for the `TextViews`. - TextView versionNumberTextView = (TextView) tabLayout.findViewById(R.id.about_version_number); - TextView versionBrandTextView = (TextView) tabLayout.findViewById(R.id.about_version_brand); - TextView versionManufacturerTextView = (TextView) tabLayout.findViewById(R.id.about_version_manufacturer); - TextView versionModelTextView = (TextView) tabLayout.findViewById(R.id.about_version_model); - TextView versionDeviceTextView = (TextView) tabLayout.findViewById(R.id.about_version_device); - TextView versionBootloaderTextView = (TextView) tabLayout.findViewById(R.id.about_version_bootloader); - TextView versionRadioTextView = (TextView) tabLayout.findViewById(R.id.about_version_radio); - TextView versionAndroidTextView = (TextView) tabLayout.findViewById(R.id.about_version_android); - TextView versionBuildTextView = (TextView) tabLayout.findViewById(R.id.about_version_build); - TextView versionSecurityPatchTextView = (TextView) tabLayout.findViewById(R.id.about_version_securitypatch); - TextView versionWebKitTextView = (TextView) tabLayout.findViewById(R.id.about_version_webkit); - TextView versionChromeText = (TextView) tabLayout.findViewById(R.id.about_version_chrome); - - // Setup the labels. - String version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + Integer.toString(BuildConfig.VERSION_CODE) + ")"; - String brandLabel = getString(R.string.brand) + " "; - String manufacturerLabel = getString(R.string.manufacturer) + " "; - String modelLabel = getString(R.string.model) + " "; - String deviceLabel = getString(R.string.device) + " "; - String bootloaderLabel = getString(R.string.bootloader) + " "; - String androidLabel = getString(R.string.android) + " "; - String buildLabel = getString(R.string.build) + " "; - String webKitLabel = getString(R.string.webkit) + " "; - String chromeLabel = getString(R.string.chrome) + " "; - - // `webViewLayout` is only used to get the default user agent from `bare_webview`. It is not used to render content on the screen. - View webViewLayout = inflater.inflate(R.layout.bare_webview, container, false); - WebView tabLayoutWebView = (WebView) webViewLayout.findViewById(R.id.bare_webview); - String userAgentString = tabLayoutWebView.getSettings().getUserAgentString(); - - // Get the device's information and store it in strings. - String brand = Build.BRAND; - String manufacturer = Build.MANUFACTURER; - String model = Build.MODEL; - String device = Build.DEVICE; - String bootloader = Build.BOOTLOADER; - String radio = Build.getRadioVersion(); - String android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Integer.toString(Build.VERSION.SDK_INT) + ")"; - String build = Build.DISPLAY; - // Select the substring that begins after "Safari/" and goes to the end of the string. - String webKit = userAgentString.substring(userAgentString.indexOf("Safari/") + 7); - // Select the substring that begins after "Chrome/" and goes until the next " ". - String chrome = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/"))); - - // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. - SpannableStringBuilder brandStringBuilder = new SpannableStringBuilder(brandLabel + brand); - SpannableStringBuilder manufacturerStringBuilder = new SpannableStringBuilder(manufacturerLabel + manufacturer); - SpannableStringBuilder modelStringBuilder = new SpannableStringBuilder(modelLabel + model); - SpannableStringBuilder deviceStringBuilder = new SpannableStringBuilder(deviceLabel + device); - SpannableStringBuilder bootloaderStringBuilder = new SpannableStringBuilder(bootloaderLabel + bootloader); - SpannableStringBuilder androidStringBuilder = new SpannableStringBuilder(androidLabel + android); - SpannableStringBuilder buildStringBuilder = new SpannableStringBuilder(buildLabel + build); - SpannableStringBuilder webKitStringBuilder = new SpannableStringBuilder(webKitLabel + webKit); - SpannableStringBuilder chromeStringBuilder = new SpannableStringBuilder(chromeLabel + chrome); - - // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. - @SuppressWarnings("deprecation") ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); - - // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. - brandStringBuilder.setSpan(blueColorSpan, brandLabel.length(), brandStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length(), manufacturerStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - modelStringBuilder.setSpan(blueColorSpan, modelLabel.length(), modelStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length(), deviceStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length(), bootloaderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - androidStringBuilder.setSpan(blueColorSpan, androidLabel.length(), androidStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - buildStringBuilder.setSpan(blueColorSpan, buildLabel.length(), buildStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - webKitStringBuilder.setSpan(blueColorSpan, webKitLabel.length(), webKitStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - chromeStringBuilder.setSpan(blueColorSpan, chromeLabel.length(), chromeStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - - // Display the strings. - versionNumberTextView.setText(version); - versionBrandTextView.setText(brandStringBuilder); - versionManufacturerTextView.setText(manufacturerStringBuilder); - versionModelTextView.setText(modelStringBuilder); - versionDeviceTextView.setText(deviceStringBuilder); - versionBootloaderTextView.setText(bootloaderStringBuilder); - versionAndroidTextView.setText(androidStringBuilder); - versionBuildTextView.setText(buildStringBuilder); - versionWebKitTextView.setText(webKitStringBuilder); - versionChromeText.setText(chromeStringBuilder); - - // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23. - if (Build.VERSION.SDK_INT >= 23) { - String securityPatchLabel = getString(R.string.security_patch) + " "; - String securityPatch = Build.VERSION.SECURITY_PATCH; - SpannableStringBuilder securityPatchStringBuilder = new SpannableStringBuilder(securityPatchLabel + securityPatch); - securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length(), securityPatchStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - versionSecurityPatchTextView.setText(securityPatchStringBuilder); - } else { // Hide `versionSecurityPatchTextView`. - versionSecurityPatchTextView.setVisibility(View.GONE); - } - - // Only populate `versionRadioTextView` if there is a radio in the device. - if (!radio.equals("")) { - String radioLabel = getString(R.string.radio) + " "; - SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio); - radioStringBuilder.setSpan(blueColorSpan, radioLabel.length(), radioStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - versionRadioTextView.setText(radioStringBuilder); - } else { // Hide `versionRadioTextView`. - versionRadioTextView.setVisibility(View.GONE); - } - } else { // load a WebView for all the other tabs. Tab numbers start at 0. - // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container. - // The fragment will take care of attaching the root automatically. - tabLayout = inflater.inflate(R.layout.bare_webview, container, false); - WebView tabWebView = (WebView) tabLayout; - - switch (tabNumber) { - case 1: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_permissions.html"); - break; - - case 2: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_privacy_policy.html"); - break; - - case 3: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_changelog.html"); - break; - - case 4: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_licenses.html"); - break; - - case 5: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_contributors.html"); - break; - - case 6: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_links.html"); - break; - - default: - break; - } - } - - return tabLayout; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java new file mode 100644 index 00000000..b3e9e5a4 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTabFragment.java @@ -0,0 +1,208 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.fragments; + +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.TextView; + +import com.stoutner.privacybrowser.BuildConfig; +import com.stoutner.privacybrowser.R; + +public class AboutTabFragment extends Fragment { + private int tabNumber; + + // AboutTabFragment.createTab stores the tab number in the bundle arguments so it can be referenced from onCreate(). + public static AboutTabFragment createTab(int tab) { + Bundle thisTabArguments = new Bundle(); + thisTabArguments.putInt("Tab", tab); + + AboutTabFragment thisTab = new AboutTabFragment(); + thisTab.setArguments(thisTabArguments); + return thisTab; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Store the tab number in tabNumber. + tabNumber = getArguments().getInt("Tab"); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View tabLayout; + + // Load the tabs. Tab numbers start at 0. + if (tabNumber == 0) { // Load the about tab. + // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container. + // The fragment will take care of attaching the root automatically. + tabLayout = inflater.inflate(R.layout.about_tab_version, container, false); + + // Get handles for the `TextViews`. + TextView versionNumberTextView = (TextView) tabLayout.findViewById(R.id.about_version_number); + TextView versionBrandTextView = (TextView) tabLayout.findViewById(R.id.about_version_brand); + TextView versionManufacturerTextView = (TextView) tabLayout.findViewById(R.id.about_version_manufacturer); + TextView versionModelTextView = (TextView) tabLayout.findViewById(R.id.about_version_model); + TextView versionDeviceTextView = (TextView) tabLayout.findViewById(R.id.about_version_device); + TextView versionBootloaderTextView = (TextView) tabLayout.findViewById(R.id.about_version_bootloader); + TextView versionRadioTextView = (TextView) tabLayout.findViewById(R.id.about_version_radio); + TextView versionAndroidTextView = (TextView) tabLayout.findViewById(R.id.about_version_android); + TextView versionBuildTextView = (TextView) tabLayout.findViewById(R.id.about_version_build); + TextView versionSecurityPatchTextView = (TextView) tabLayout.findViewById(R.id.about_version_securitypatch); + TextView versionWebKitTextView = (TextView) tabLayout.findViewById(R.id.about_version_webkit); + TextView versionChromeText = (TextView) tabLayout.findViewById(R.id.about_version_chrome); + + // Setup the labels. + String version = getString(R.string.version) + " " + BuildConfig.VERSION_NAME + " (" + getString(R.string.version_code) + " " + Integer.toString(BuildConfig.VERSION_CODE) + ")"; + String brandLabel = getString(R.string.brand) + " "; + String manufacturerLabel = getString(R.string.manufacturer) + " "; + String modelLabel = getString(R.string.model) + " "; + String deviceLabel = getString(R.string.device) + " "; + String bootloaderLabel = getString(R.string.bootloader) + " "; + String androidLabel = getString(R.string.android) + " "; + String buildLabel = getString(R.string.build) + " "; + String webKitLabel = getString(R.string.webkit) + " "; + String chromeLabel = getString(R.string.chrome) + " "; + + // `webViewLayout` is only used to get the default user agent from `bare_webview`. It is not used to render content on the screen. + View webViewLayout = inflater.inflate(R.layout.bare_webview, container, false); + WebView tabLayoutWebView = (WebView) webViewLayout.findViewById(R.id.bare_webview); + String userAgentString = tabLayoutWebView.getSettings().getUserAgentString(); + + // Get the device's information and store it in strings. + String brand = Build.BRAND; + String manufacturer = Build.MANUFACTURER; + String model = Build.MODEL; + String device = Build.DEVICE; + String bootloader = Build.BOOTLOADER; + String radio = Build.getRadioVersion(); + String android = Build.VERSION.RELEASE + " (" + getString(R.string.api) + " " + Integer.toString(Build.VERSION.SDK_INT) + ")"; + String build = Build.DISPLAY; + // Select the substring that begins after "Safari/" and goes to the end of the string. + String webKit = userAgentString.substring(userAgentString.indexOf("Safari/") + 7); + // Select the substring that begins after "Chrome/" and goes until the next " ". + String chrome = userAgentString.substring(userAgentString.indexOf("Chrome/") + 7, userAgentString.indexOf(" ", userAgentString.indexOf("Chrome/"))); + + // Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text. + SpannableStringBuilder brandStringBuilder = new SpannableStringBuilder(brandLabel + brand); + SpannableStringBuilder manufacturerStringBuilder = new SpannableStringBuilder(manufacturerLabel + manufacturer); + SpannableStringBuilder modelStringBuilder = new SpannableStringBuilder(modelLabel + model); + SpannableStringBuilder deviceStringBuilder = new SpannableStringBuilder(deviceLabel + device); + SpannableStringBuilder bootloaderStringBuilder = new SpannableStringBuilder(bootloaderLabel + bootloader); + SpannableStringBuilder androidStringBuilder = new SpannableStringBuilder(androidLabel + android); + SpannableStringBuilder buildStringBuilder = new SpannableStringBuilder(buildLabel + build); + SpannableStringBuilder webKitStringBuilder = new SpannableStringBuilder(webKitLabel + webKit); + SpannableStringBuilder chromeStringBuilder = new SpannableStringBuilder(chromeLabel + chrome); + + // Create a blue `ForegroundColorSpan`. We have to use the deprecated `getColor` until API >= 23. + @SuppressWarnings("deprecation") ForegroundColorSpan blueColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.blue_700)); + + // Setup the spans to display the device information in blue. `SPAN_INCLUSIVE_INCLUSIVE` allows the span to grow in either direction. + brandStringBuilder.setSpan(blueColorSpan, brandLabel.length(), brandStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + manufacturerStringBuilder.setSpan(blueColorSpan, manufacturerLabel.length(), manufacturerStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + modelStringBuilder.setSpan(blueColorSpan, modelLabel.length(), modelStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + deviceStringBuilder.setSpan(blueColorSpan, deviceLabel.length(), deviceStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + bootloaderStringBuilder.setSpan(blueColorSpan, bootloaderLabel.length(), bootloaderStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + androidStringBuilder.setSpan(blueColorSpan, androidLabel.length(), androidStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + buildStringBuilder.setSpan(blueColorSpan, buildLabel.length(), buildStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + webKitStringBuilder.setSpan(blueColorSpan, webKitLabel.length(), webKitStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + chromeStringBuilder.setSpan(blueColorSpan, chromeLabel.length(), chromeStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + + // Display the strings. + versionNumberTextView.setText(version); + versionBrandTextView.setText(brandStringBuilder); + versionManufacturerTextView.setText(manufacturerStringBuilder); + versionModelTextView.setText(modelStringBuilder); + versionDeviceTextView.setText(deviceStringBuilder); + versionBootloaderTextView.setText(bootloaderStringBuilder); + versionAndroidTextView.setText(androidStringBuilder); + versionBuildTextView.setText(buildStringBuilder); + versionWebKitTextView.setText(webKitStringBuilder); + versionChromeText.setText(chromeStringBuilder); + + // Build.VERSION.SECURITY_PATCH is only available for SDK_INT >= 23. + if (Build.VERSION.SDK_INT >= 23) { + String securityPatchLabel = getString(R.string.security_patch) + " "; + String securityPatch = Build.VERSION.SECURITY_PATCH; + SpannableStringBuilder securityPatchStringBuilder = new SpannableStringBuilder(securityPatchLabel + securityPatch); + securityPatchStringBuilder.setSpan(blueColorSpan, securityPatchLabel.length(), securityPatchStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + versionSecurityPatchTextView.setText(securityPatchStringBuilder); + } else { // Hide `versionSecurityPatchTextView`. + versionSecurityPatchTextView.setVisibility(View.GONE); + } + + // Only populate `versionRadioTextView` if there is a radio in the device. + if (!radio.equals("")) { + String radioLabel = getString(R.string.radio) + " "; + SpannableStringBuilder radioStringBuilder = new SpannableStringBuilder(radioLabel + radio); + radioStringBuilder.setSpan(blueColorSpan, radioLabel.length(), radioStringBuilder.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + versionRadioTextView.setText(radioStringBuilder); + } else { // Hide `versionRadioTextView`. + versionRadioTextView.setVisibility(View.GONE); + } + } else { // load a WebView for all the other tabs. Tab numbers start at 0. + // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container. + // The fragment will take care of attaching the root automatically. + tabLayout = inflater.inflate(R.layout.bare_webview, container, false); + WebView tabWebView = (WebView) tabLayout; + + switch (tabNumber) { + case 1: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_permissions.html"); + break; + + case 2: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_privacy_policy.html"); + break; + + case 3: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_changelog.html"); + break; + + case 4: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_licenses.html"); + break; + + case 5: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_contributors.html"); + break; + + case 6: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/about_links.html"); + break; + + default: + break; + } + } + + return tabLayout; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java new file mode 100644 index 00000000..3e28fc7f --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.fragments; + +import android.database.Cursor; +import android.os.Bundle; +// We have to use `android.support.v4.app.Fragment` until minimum API >= 23. Otherwise we cannot call `getContext()`. +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper; + +public class DomainSettingsFragment extends Fragment { + // `DATABASE_ID` is used by activities calling this fragment. + public static final String DATABASE_ID = "database_id"; + + // `databaseId` is used in `onCreate()` and `onCreateView()`. + private int databaseId; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Store the database id in `databaseId`. + databaseId = getArguments().getInt(DATABASE_ID); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate `domain_settings`. `false` does not attach it to the root `container`. + View domainSettingsView = inflater.inflate(R.layout.domain_settings, container, false); + + // Initialize the database handler. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`. + // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`. + DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(getContext(), null, null, 0); + + // Get the database `Cursor` for this ID and move it to the first row. + Cursor domainCursor = domainsDatabaseHelper.getCursorForId(databaseId); + domainCursor.moveToFirst(); + + // Get handles for the `EditTexts`. + EditText domainNameEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_name_edittext); + + // Set the text from the database cursor. + domainNameEditText.setText(domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN))); + + return domainSettingsView; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTab.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTab.java deleted file mode 100644 index 8857aca4..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTab.java +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright 2016 Soren Stoutner . - * - * This file is part of 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 . - */ - -package com.stoutner.privacybrowser.fragments; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebView; - -import com.stoutner.privacybrowser.R; - -public class GuideTab extends Fragment { - private int tabNumber; - - // GuideTab.createTab stores the tab number in the bundle arguments so it can be referenced from onCreate(). - public static GuideTab createTab (int tab) { - Bundle thisTabArguments = new Bundle(); - thisTabArguments.putInt("Tab", tab); - - GuideTab thisTab = new GuideTab(); - thisTab.setArguments(thisTabArguments); - return thisTab; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Store the tab number in tabNumber. - tabNumber = getArguments().getInt("Tab"); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container. - // The fragment will take care of attaching the root automatically. - View tabLayout = inflater.inflate(R.layout.guide_tab_webview, container, false); - WebView tabWebView = (WebView) tabLayout; - - // Tab numbers start at 0. - switch (tabNumber) { - case 0: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_overview.html"); - break; - - case 1: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_javascript.html"); - break; - - case 2: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_local_storage.html"); - break; - - case 3: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_user_agent.html"); - break; - - case 4: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_tor.html"); - break; - - case 5: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_tracking_ids.html"); - break; - - case 6: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_clear_and_exit.html"); - break; - - case 7: - tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_planned_features.html"); - break; - - default: - break; - } - - return tabLayout; - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java new file mode 100644 index 00000000..5c8a07b5 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTabFragment.java @@ -0,0 +1,100 @@ +/* + * Copyright 2016-2017 Soren Stoutner . + * + * This file is part of 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 . + */ + +package com.stoutner.privacybrowser.fragments; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; + +import com.stoutner.privacybrowser.R; + +public class GuideTabFragment extends Fragment { + // `tabNumber` is used in `onCreate()` and `onCreateView()`. + private int tabNumber; + + // GuideTabFragment.createTab stores the tab number in the bundle arguments so it can be referenced from onCreate(). + public static GuideTabFragment createTab (int tab) { + Bundle thisTabArguments = new Bundle(); + thisTabArguments.putInt("Tab", tab); + + GuideTabFragment thisTab = new GuideTabFragment(); + thisTab.setArguments(thisTabArguments); + return thisTab; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Store the tab number in `tabNumber`. + tabNumber = getArguments().getInt("Tab"); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Setting false at the end of inflater.inflate does not attach the inflated layout as a child of container. + // The fragment will take care of attaching the root automatically. + View tabLayout = inflater.inflate(R.layout.guide_tab_webview, container, false); + WebView tabWebView = (WebView) tabLayout; + + // Tab numbers start at 0. + switch (tabNumber) { + case 0: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_overview.html"); + break; + + case 1: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_javascript.html"); + break; + + case 2: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_local_storage.html"); + break; + + case 3: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_user_agent.html"); + break; + + case 4: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_tor.html"); + break; + + case 5: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_tracking_ids.html"); + break; + + case 6: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_clear_and_exit.html"); + break; + + case 7: + tabWebView.loadUrl("file:///android_asset/" + getString(R.string.android_asset_path) + "/guide_planned_features.html"); + break; + + default: + break; + } + + return tabLayout; + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java index 9fee2931..0f7330bb 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java @@ -1,5 +1,5 @@ -/** - * Copyright 2016 Soren Stoutner . +/* + * Copyright 2016-2017 Soren Stoutner . * * This file is part of Privacy Browser . * diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/OrbotProxyHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/OrbotProxyHelper.java index 8d0e4082..154e6d10 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/OrbotProxyHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/OrbotProxyHelper.java @@ -1,4 +1,4 @@ -/** +/* * Copyright 2016-2017 Soren Stoutner . * * This file is part of Privacy Browser . @@ -30,7 +30,7 @@ import android.support.v7.app.AlertDialog; import android.util.ArrayMap; import android.util.Log; -import com.stoutner.privacybrowser.activities.MainWebView; +import com.stoutner.privacybrowser.activities.MainWebViewActivity; import com.stoutner.privacybrowser.R; import java.lang.reflect.Field; @@ -78,14 +78,14 @@ public class OrbotProxyHelper { if (proxyPort.equals("8118")) { // Orbot proxy was turned on. // Set the `appBar` background to be light blue if Orbot proxy support is enabled. - MainWebView.appBar.setBackgroundDrawable(ContextCompat.getDrawable(privacyBrowserContext, R.color.blue_50)); + MainWebViewActivity.appBar.setBackgroundDrawable(ContextCompat.getDrawable(privacyBrowserContext, R.color.blue_50)); try { // Check to see if Orbot is installed. PackageManager packageManager = privacyBrowserContext.getPackageManager(); packageManager.getPackageInfo("org.torproject.android", PackageManager.GET_ACTIVITIES); // Ask Orbot to connect if its current status is not "ON". - if (!MainWebView.orbotStatus.equals("ON")) { + if (!MainWebViewActivity.orbotStatus.equals("ON")) { // Request Orbot to start. Intent orbotIntent = new Intent("org.torproject.android.intent.action.START"); @@ -114,7 +114,7 @@ public class OrbotProxyHelper { alertDialog.show(); } } else { // Otherwise set the default grey `appBar` background. - MainWebView.appBar.setBackgroundDrawable(ContextCompat.getDrawable(privacyBrowserContext, R.color.gray_100)); + MainWebViewActivity.appBar.setBackgroundDrawable(ContextCompat.getDrawable(privacyBrowserContext, R.color.gray_100)); } } } diff --git a/app/src/main/res/drawable/bookmarks_list_selector.xml b/app/src/main/res/drawable/bookmarks_list_selector.xml index 30e99383..3f74269f 100644 --- a/app/src/main/res/drawable/bookmarks_list_selector.xml +++ b/app/src/main/res/drawable/bookmarks_list_selector.xml @@ -1,7 +1,7 @@ - + . --> - + + android:drawable="@color/blue_200" /> \ No newline at end of file diff --git a/app/src/main/res/layout-w900dp/domains_list.xml b/app/src/main/res/layout-w900dp/domains_list.xml index 4d6df86c..48428580 100644 --- a/app/src/main/res/layout-w900dp/domains_list.xml +++ b/app/src/main/res/layout-w900dp/domains_list.xml @@ -22,7 +22,7 @@ + - + android:layout_weight="3" + android:orientation="horizontal" /> \ No newline at end of file diff --git a/app/src/main/res/layout/bare_webview.xml b/app/src/main/res/layout/bare_webview.xml index 9e628092..7960ca5e 100644 --- a/app/src/main/res/layout/bare_webview.xml +++ b/app/src/main/res/layout/bare_webview.xml @@ -18,7 +18,7 @@ You should have received a copy of the GNU General Public License along with Privacy Browser. If not, see . --> - + - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/domain_settings.xml b/app/src/main/res/layout/domain_settings.xml new file mode 100644 index 00000000..a71cd610 --- /dev/null +++ b/app/src/main/res/layout/domain_settings.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/domain_settings_coordinatorlayout.xml b/app/src/main/res/layout/domain_settings_coordinatorlayout.xml new file mode 100644 index 00000000..7c47eba9 --- /dev/null +++ b/app/src/main/res/layout/domain_settings_coordinatorlayout.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/domains_coordinatorlayout.xml b/app/src/main/res/layout/domains_coordinatorlayout.xml new file mode 100644 index 00000000..aef0d67b --- /dev/null +++ b/app/src/main/res/layout/domains_coordinatorlayout.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/domains_list.xml b/app/src/main/res/layout/domains_list.xml index 6864bcbc..4f5ad03a 100644 --- a/app/src/main/res/layout/domains_list.xml +++ b/app/src/main/res/layout/domains_list.xml @@ -23,7 +23,7 @@ android:id="@+id/domains_listview" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - tools:context=".activities.DomainsList" + tools:context=".activities.DomainsActivity" android:layout_height="match_parent" android:layout_width="match_parent" android:divider="@color/white" diff --git a/app/src/main/res/layout/domains_list_coordinatorlayout.xml b/app/src/main/res/layout/domains_list_coordinatorlayout.xml deleted file mode 100644 index 7f870a92..00000000 --- a/app/src/main/res/layout/domains_list_coordinatorlayout.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/drawerlayout.xml b/app/src/main/res/layout/drawerlayout.xml index 5114b8ef..ca8cffed 100644 --- a/app/src/main/res/layout/drawerlayout.xml +++ b/app/src/main/res/layout/drawerlayout.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/menu/webview_options_menu.xml b/app/src/main/res/menu/webview_options_menu.xml index 06fa1ac4..8f0df746 100644 --- a/app/src/main/res/menu/webview_options_menu.xml +++ b/app/src/main/res/menu/webview_options_menu.xml @@ -22,7 +22,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - tools:context=".activities.MainWebView"> + tools:context=".activities.MainWebViewActivity"> Configuración de Navegador Privado es - + Modo Privado Javascript habilitado Javascript deshabilitado @@ -83,7 +83,7 @@ URL URL: - + Caja de navegación Navegación Inicio @@ -98,7 +98,7 @@ Acerca de Eliminar y salir - + Javascript Cookies de primera parte Cookies de terceras partes diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7689f4d3..efe7d613 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -185,6 +185,7 @@ Domains + Domain Settings Add Domain Add Domain Name