From: Soren Stoutner Date: Wed, 16 Nov 2016 04:16:23 +0000 (-0700) Subject: Create Java subpackage folders. X-Git-Tag: v1.13~11 X-Git-Url: https://gitweb.stoutner.com/?a=commitdiff_plain;h=ae2ee09aa7a2afc19f5603c9bc021f98888d7b78;p=PrivacyBrowserAndroid.git Create Java subpackage folders. --- diff --git a/app/build.gradle b/app/build.gradle index 04c77a8a..5e97dbee 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,9 +57,9 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) + compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:design:24.2.1' - // Only compile `com.google.android.gms:play-services-ads` for the free flavor. + // Only compile `com.google.firebase:firebase-ads:9.8.0` for the free flavor. freeCompile 'com.google.firebase:firebase-ads:9.8.0' } diff --git a/app/src/free/java/com/stoutner/privacybrowser/BannerAd.java b/app/src/free/java/com/stoutner/privacybrowser/BannerAd.java index cc933817..3f9d9a73 100644 --- a/app/src/free/java/com/stoutner/privacybrowser/BannerAd.java +++ b/app/src/free/java/com/stoutner/privacybrowser/BannerAd.java @@ -28,8 +28,8 @@ import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdSize; import com.google.android.gms.ads.AdView; -class BannerAd extends AppCompatActivity{ - static void requestAd(View view) { +public class BannerAd extends AppCompatActivity{ + public static void requestAd(View view) { // Cast view to an AdView. AdView adView = (AdView) view; @@ -38,7 +38,7 @@ class BannerAd extends AppCompatActivity{ adView.loadAd(adRequest); } - static void reloadAfterRotate (View view, Context applicationContext, String ad_id) { + public static void reloadAfterRotate (View view, Context applicationContext, String ad_id) { // Cast view to an AdView. AdView adView = (AdView) view; @@ -64,7 +64,7 @@ class BannerAd extends AppCompatActivity{ adView.loadAd(adRequest); } - static void hideAd(View view) { + public static void hideAd(View view) { // Cast view to an AdView. AdView adView = (AdView) view; @@ -72,7 +72,7 @@ class BannerAd extends AppCompatActivity{ adView.setVisibility(View.GONE); } - static void showAd(View view) { + public static void showAd(View view) { // Cast view to an AdView. AdView adView = (AdView) view; @@ -80,7 +80,7 @@ class BannerAd extends AppCompatActivity{ adView.setVisibility(View.VISIBLE); } - static void pauseAd(View view) { + public static void pauseAd(View view) { // Cast view to an AdView. AdView adView = (AdView) view; @@ -88,7 +88,7 @@ class BannerAd extends AppCompatActivity{ adView.pause(); } - static void resumeAd(View view) { + public static void resumeAd(View view) { // Cast view to an AdView. AdView adView = (AdView) view; diff --git a/app/src/free/res/layout/main_webview.xml b/app/src/free/res/layout/main_webview.xml index 403bb649..a342a0e7 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.MainWebViewActivity" + tools:context="com.stoutner.privacybrowser.activities.MainWebView" tools:showIn="@layout/coordinator_layout"> . - * - * 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; - -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; - -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); - } - - 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 AboutTabFragment.createTab(tab); - } - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/AboutTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/AboutTabFragment.java deleted file mode 100644 index baddf5c3..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/AboutTabFragment.java +++ /dev/null @@ -1,205 +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; - -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; - -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(buildStringBuilder); - 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/BookmarksActivity.java b/app/src/main/java/com/stoutner/privacybrowser/BookmarksActivity.java deleted file mode 100644 index 15264c69..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/BookmarksActivity.java +++ /dev/null @@ -1,876 +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; - -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 java.io.ByteArrayOutputStream; - -public class BookmarksActivity extends AppCompatActivity implements CreateBookmark.CreateBookmarkListener, - CreateBookmarkFolder.CreateBookmarkFolderListener, EditBookmark.EditBookmarkListener, - EditBookmarkFolder.EditBookmarkFolderListener, MoveToFolder.MoveToFolderListener { - - // `bookmarksDatabaseHandler` is public static so it can be accessed from `EditBookmark` and `MoveToFolder`. It is also used in `onCreate()`, - // `onCreateBookmarkCreate()`, `updateBookmarksListView()`, and `updateBookmarksListViewExcept()`. - public static BookmarksDatabaseHandler bookmarksDatabaseHandler; - - // `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 `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 `BookmarksDatabaseHandler`. - bookmarksDatabaseHandler = new BookmarksDatabaseHandler(this, null, null, 0); - bookmarksListView = (ListView) findViewById(R.id.bookmarks_listview); - - // Set currentFolder to the home folder, which is null 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` and move it to the first row. - Cursor bookmarkCursor = bookmarksDatabaseHandler.getBookmarkCursor(databaseID); - bookmarkCursor.moveToFirst(); - - // If the bookmark is a folder load its contents into the ListView. - if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHandler.IS_FOLDER)) == 1) { - // Update `currentFolder`. - currentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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; - - // Sometimes Android forgets to close the contextual app bar when all the items are deselected. - if (numberOfSelectedBookmarks == 0) { - mode.finish(); - } - - // List the number of selected bookmarks in the subtitle. - mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected)); - - if (numberOfSelectedBookmarks == 1) { - // Show the `Move Up`, `Move Down`, and `Edit` option only if 1 bookmark is selected. - 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 { // Hide the MenuItems because more than one bookmark is selected. - 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. - bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i - 1); - selectedBookmarkNewPosition = i - 1; - } else { // Move the bookmark above the selected bookmark down one. - bookmarksDatabaseHandler.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. - bookmarksDatabaseHandler.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 = 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); - bookmarksDatabaseHandler.updateBookmarkDisplayOrder(databaseId, i + 1); - } - - // Create the folder, placing it at the top of the ListView - bookmarksDatabaseHandler.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. - bookmarksDatabaseHandler.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. - bookmarksDatabaseHandler.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 = bookmarksDatabaseHandler.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)) { - bookmarksDatabaseHandler.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(); - - bookmarksDatabaseHandler.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_rename_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_LONG).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 = bookmarksDatabaseHandler.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. - bookmarksDatabaseHandler.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 = bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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 = bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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 = bookmarksDatabaseHandler.getFolderName(databaseId); - - // Get the contents of the folder. - Cursor folderCursor = bookmarksDatabaseHandler.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(BookmarksDatabaseHandler._ID)); - - // If this is a folder, delete the contents first. - if (bookmarksDatabaseHandler.isFolder(itemDatabaseId)) { - deleteBookmarkFolderContents(itemDatabaseId); - } - - bookmarksDatabaseHandler.deleteBookmark(itemDatabaseId); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java b/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java deleted file mode 100644 index 3d4b7f81..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseHandler.java +++ /dev/null @@ -1,448 +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; - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -class BookmarksDatabaseHandler extends SQLiteOpenHelper { - private static final int SCHEMA_VERSION = 1; - private static final String BOOKMARKS_DATABASE = "bookmarks.db"; - private static final String BOOKMARKS_TABLE = "bookmarks"; - - static final String _ID = "_id"; - static final String DISPLAY_ORDER = "displayorder"; - static final String BOOKMARK_NAME = "bookmarkname"; - static final String BOOKMARK_URL = "bookmarkurl"; - static final String PARENT_FOLDER = "parentfolder"; - static final String IS_FOLDER = "isfolder"; - static final String FAVORITE_ICON = "favoriteicon"; - - // Initialize the database. The lint warnings for the unused parameters are suppressed. - BookmarksDatabaseHandler(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory factory, @SuppressWarnings("UnusedParameters") int version) { - super(context, BOOKMARKS_DATABASE, factory, SCHEMA_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase bookmarksDatabase) { - // Create the database if it doesn't exist. - String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" + - _ID + " integer primary key, " + - DISPLAY_ORDER + " integer, " + - BOOKMARK_NAME + " text, " + - BOOKMARK_URL + " text, " + - PARENT_FOLDER + " text, " + - IS_FOLDER + " boolean, " + - FAVORITE_ICON + " blob);"; - - bookmarksDatabase.execSQL(CREATE_BOOKMARKS_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase bookmarksDatabase, int oldVersion, int newVersion) { - // Code for upgrading the database will be added here when the schema version > 1. - } - - void createBookmark(String bookmarkName, String bookmarkURL, int displayOrder, String parentFolder, byte[] favoriteIcon) { - ContentValues bookmarkContentValues = new ContentValues(); - - // ID is created automatically. - bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); - bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName); - bookmarkContentValues.put(BOOKMARK_URL, bookmarkURL); - bookmarkContentValues.put(PARENT_FOLDER, parentFolder); - bookmarkContentValues.put(IS_FOLDER, false); - bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon); - - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // The second argument is `null`, which makes it so that completely null rows cannot be created. Not a problem in our case. - bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues); - - // Close the database handle. - bookmarksDatabase.close(); - } - - void createFolder(String folderName, int displayOrder, String parentFolder, byte[] favoriteIcon) { - ContentValues bookmarkContentValues = new ContentValues(); - - // ID is created automatically. - bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); - bookmarkContentValues.put(BOOKMARK_NAME, folderName); - bookmarkContentValues.put(PARENT_FOLDER, parentFolder); - bookmarkContentValues.put(IS_FOLDER, true); - bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon); - - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // The second argument is `null`, which makes it so that completely null rows cannot be created. Not a problem in our case. - bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues); - - // Close the database handle. - bookmarksDatabase.close(); - } - - Cursor getBookmarkCursor(int databaseId) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // Prepare the SQL statement to get the `Cursor` for `databaseId` - final String GET_ONE_BOOKMARK = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + _ID + " = " + databaseId; - - // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. - // We can't close the `Cursor` because we need to use it in the parent activity. - return bookmarksDatabase.rawQuery(GET_ONE_BOOKMARK, null); - } - - String getFolderName (int databaseId) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // Prepare the SQL statement to get the `Cursor` for the folder. - final String GET_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + _ID + " = " + databaseId; - - // Get `folderCursor`. The second argument is `null` because there are no `selectionArgs`. - Cursor folderCursor = bookmarksDatabase.rawQuery(GET_FOLDER, null); - - // Get `folderName`. - folderCursor.moveToFirst(); - String folderName = folderCursor.getString(folderCursor.getColumnIndex(BOOKMARK_NAME)); - - // Close the cursor and the database handle. - folderCursor.close(); - bookmarksDatabase.close(); - - // Return the folder name. - return folderName; - } - - Cursor getFolderCursor(String folderName) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // SQL escape `folderName`. - folderName = DatabaseUtils.sqlEscapeString(folderName); - - // Prepare the SQL statement to get the `Cursor` for the folder. - final String GET_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + BOOKMARK_NAME + " = " + folderName + - " AND " + IS_FOLDER + " = " + 1; - - // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. - // We can't close the `Cursor` because we need to use it in the parent activity. - return bookmarksDatabase.rawQuery(GET_FOLDER, null); - } - - Cursor getFoldersCursorExcept(String exceptFolders) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // Prepare the SQL statement to get the `Cursor` for the folders. - final String GET_FOLDERS_EXCEPT = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + IS_FOLDER + " = " + 1 + - " AND " + BOOKMARK_NAME + " NOT IN (" + exceptFolders + - ") ORDER BY " + BOOKMARK_NAME + " ASC"; - - // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. - // We can't close the `Cursor` because we need to use it in the parent activity. - return bookmarksDatabase.rawQuery(GET_FOLDERS_EXCEPT, null); - } - - Cursor getSubfoldersCursor(String currentFolder) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // SQL escape `currentFolder. - currentFolder = DatabaseUtils.sqlEscapeString(currentFolder); - - // Prepare the SQL statement to get the `Cursor` for the subfolders. - final String GET_SUBFOLDERS = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + PARENT_FOLDER + " = " + currentFolder + - " AND " + IS_FOLDER + " = " + 1; - - // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. - // We can't close the `Cursor` because we need to use it in the parent activity. - return bookmarksDatabase.rawQuery(GET_SUBFOLDERS, null); - } - - String getParentFolder(String currentFolder) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // SQL escape `currentFolder`. - currentFolder = DatabaseUtils.sqlEscapeString(currentFolder); - - // Prepare the SQL statement to get the parent folder. - final String GET_PARENT_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + IS_FOLDER + " = " + 1 + - " AND " + BOOKMARK_NAME + " = " + currentFolder; - - // The second argument is `null` because there are no `selectionArgs`. - Cursor bookmarkCursor = bookmarksDatabase.rawQuery(GET_PARENT_FOLDER, null); - bookmarkCursor.moveToFirst(); - - // Store the name of the parent folder. - String parentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(PARENT_FOLDER)); - - // Close the `Cursor`. - bookmarkCursor.close(); - - return parentFolder; - } - - Cursor getAllBookmarksCursor() { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // Get everything in the BOOKMARKS_TABLE. - final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE; - - // Return the results as a Cursor. The second argument is `null` because there are no selectionArgs. - // We can't close the Cursor because we need to use it in the parent activity. - return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null); - } - - Cursor getAllBookmarksCursorByDisplayOrder(String folderName) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // SQL escape `folderName`. - folderName = DatabaseUtils.sqlEscapeString(folderName); - - // Get everything in the BOOKMARKS_TABLE. - final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + PARENT_FOLDER + " = " + folderName + - " ORDER BY " + DISPLAY_ORDER + " ASC"; - - // Return the results as a Cursor. The second argument is `null` because there are no selectionArgs. - // We can't close the Cursor because we need to use it in the parent activity. - return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null); - } - - Cursor getBookmarksCursorExcept(long[] exceptIdLongArray, String folderName) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // Prepare a string that contains the comma-separated list of IDs not to get. - String doNotGetIdsString = ""; - // Extract the array to `doNotGetIdsString`. - for (long databaseIdLong : exceptIdLongArray) { - // If this is the first number, only add the number. - if (doNotGetIdsString.isEmpty()) { - doNotGetIdsString = String.valueOf(databaseIdLong); - } else { // If there already is a number in the string, place a `,` before the number. - doNotGetIdsString = doNotGetIdsString + "," + databaseIdLong; - } - } - - // SQL escape `folderName`. - folderName = DatabaseUtils.sqlEscapeString(folderName); - - // Prepare the SQL statement to select all items except those with the specified IDs. - final String GET_All_BOOKMARKS_EXCEPT_SPECIFIED = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + PARENT_FOLDER + " = " + folderName + - " AND " + _ID + " NOT IN (" + doNotGetIdsString + - ") ORDER BY " + DISPLAY_ORDER + " ASC"; - - // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. - // We can't close the `Cursor` because we need to use it in the parent activity. - return bookmarksDatabase.rawQuery(GET_All_BOOKMARKS_EXCEPT_SPECIFIED, null); - } - - boolean isFolder(int databaseId) { - // Get a readable database handle. - SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); - - // Prepare the SQL statement to determine if `databaseId` is a folder. - final String CHECK_IF_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + _ID + " = " + databaseId; - - // Populate folderCursor. The second argument is `null` because there are no `selectionArgs`. - Cursor folderCursor = bookmarksDatabase.rawQuery(CHECK_IF_FOLDER, null); - - // Ascertain if this database ID is a folder. - folderCursor.moveToFirst(); - boolean isFolder = (folderCursor.getInt(folderCursor.getColumnIndex(IS_FOLDER)) == 1); - - // Close the `Cursor` and the database handle. - folderCursor.close(); - bookmarksDatabase.close(); - - return isFolder; - } - - void updateBookmark(int databaseId, String bookmarkName, String bookmarkUrl) { - // Store the updated values in `bookmarkContentValues`. - ContentValues bookmarkContentValues = new ContentValues(); - - bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName); - bookmarkContentValues.put(BOOKMARK_URL, bookmarkUrl); - - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // Update the bookmark. The last argument is `null` because there are no `whereArgs`. - bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); - - // Close the database handle. - bookmarksDatabase.close(); - } - - void updateBookmark(int databaseId, String bookmarkName, String bookmarkUrl, byte[] favoriteIcon) { - // Store the updated values in `bookmarkContentValues`. - ContentValues bookmarkContentValues = new ContentValues(); - - bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName); - bookmarkContentValues.put(BOOKMARK_URL, bookmarkUrl); - bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon); - - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // Update the bookmark. The last argument is `null` because there are no `whereArgs`. - bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); - - // Close the database handle. - bookmarksDatabase.close(); - } - - void updateFolder(int databaseId, String oldFolderName, String newFolderName) { - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // Update the folder first. Store the updated values in `folderContentValues`. - ContentValues folderContentValues = new ContentValues(); - - folderContentValues.put(BOOKMARK_NAME, newFolderName); - - // Run the update on the folder. The last argument is `null` because there are no `whereArgs`. - bookmarksDatabase.update(BOOKMARKS_TABLE, folderContentValues, _ID + " = " + databaseId, null); - - - // Update the bookmarks inside the folder with the new parent folder name. - ContentValues bookmarkContentValues = new ContentValues(); - - bookmarkContentValues.put(PARENT_FOLDER, newFolderName); - - // SQL escape `oldFolderName`. - oldFolderName = DatabaseUtils.sqlEscapeString(oldFolderName); - - // Run the update on the bookmarks. The last argument is `null` because there are no `whereArgs`. - bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, PARENT_FOLDER + " = " + oldFolderName, null); - - // Close the database handle. - bookmarksDatabase.close(); - } - - void updateFolder(int databaseId, String oldFolderName, String newFolderName, byte[] folderIcon) { - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // Update the folder first. Store the updated values in `folderContentValues`. - ContentValues folderContentValues = new ContentValues(); - - folderContentValues.put(BOOKMARK_NAME, newFolderName); - folderContentValues.put(FAVORITE_ICON, folderIcon); - - // Run the update on the folder. The last argument is `null` because there are no `whereArgs`. - bookmarksDatabase.update(BOOKMARKS_TABLE, folderContentValues, _ID + " = " + databaseId, null); - - - // Update the bookmarks inside the folder with the new parent folder name. - ContentValues bookmarkContentValues = new ContentValues(); - - bookmarkContentValues.put(PARENT_FOLDER, newFolderName); - - // SQL escape `oldFolderName`. - oldFolderName = DatabaseUtils.sqlEscapeString(oldFolderName); - - // Run the update on the bookmarks. The last argument is `null` because there are no `whereArgs`. - bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, PARENT_FOLDER + " = " + oldFolderName, null); - - // Close the database handle. - bookmarksDatabase.close(); - } - - void updateBookmarkDisplayOrder(int databaseId, int displayOrder) { - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // Store the new display order in `bookmarkContentValues`. - ContentValues bookmarkContentValues = new ContentValues(); - bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); - - // Update the database. The last argument is `null` because there are no `whereArgs`. - bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); - - // Close the database handle. - bookmarksDatabase.close(); - } - - void moveToFolder(int databaseId, String newFolder) { - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // Get the highest `DISPLAY_ORDER` in the new folder - String newFolderSqlEscaped = DatabaseUtils.sqlEscapeString(newFolder); - final String NEW_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + - " WHERE " + PARENT_FOLDER + " = " + newFolderSqlEscaped + - " ORDER BY " + DISPLAY_ORDER + " ASC"; - // The second argument is `null` because there are no `selectionArgs`. - Cursor newFolderCursor = bookmarksDatabase.rawQuery(NEW_FOLDER, null); - int displayOrder; - if (newFolderCursor.getCount() > 0) { - newFolderCursor.moveToLast(); - displayOrder = newFolderCursor.getInt(newFolderCursor.getColumnIndex(DISPLAY_ORDER)) + 1; - } else { - displayOrder = 0; - } - newFolderCursor.close(); - - // Store the new values in `bookmarkContentValues`. - ContentValues bookmarkContentValues = new ContentValues(); - bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); - bookmarkContentValues.put(PARENT_FOLDER, newFolder); - - // Update the database. The last argument is 'null' because there are no 'whereArgs'. - bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); - - // Close the database handle. - bookmarksDatabase.close(); - } - - void deleteBookmark(int databaseId) { - // Get a writable database handle. - SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); - - // Deletes the row with the given databaseId. The last argument is null because we don't need additional parameters. - bookmarksDatabase.delete(BOOKMARKS_TABLE, _ID + " = " + databaseId, null); - - // Close the database handle. - bookmarksDatabase.close(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseViewActivity.java deleted file mode 100644 index a24e6948..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/BookmarksDatabaseViewActivity.java +++ /dev/null @@ -1,147 +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; - -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; - -public class BookmarksDatabaseViewActivity extends AppCompatActivity { - // `bookmarksDatabaseHandler` is used in `onCreate()` and `updateBookmarksListView()`. - private BookmarksDatabaseHandler bookmarksDatabaseHandler; - - // `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 `BookmarksDatabaseHandler`. - bookmarksDatabaseHandler = new BookmarksDatabaseHandler(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 = bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.IS_FOLDER)) == 1); - - // Get the database ID from the `Cursor` and display it in `bookmarkDatabaseIdTextView`. - int bookmarkDatabaseId = cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHandler._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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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_grey)); - bookmarkParentFolderTextView.setText(R.string.home_folder); - bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.grey_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(BookmarksDatabaseHandler.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/CreateBookmarkFolder.java b/app/src/main/java/com/stoutner/privacybrowser/CreateBookmarkFolder.java deleted file mode 100644 index 46d2ed96..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/CreateBookmarkFolder.java +++ /dev/null @@ -1,126 +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; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.support.annotation.NonNull; -// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. -import android.support.v7.app.AlertDialog; -// 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; - -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(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/CreateHomeScreenShortcut.java b/app/src/main/java/com/stoutner/privacybrowser/CreateHomeScreenShortcut.java deleted file mode 100644 index 6159f8da..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/CreateHomeScreenShortcut.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright 2015-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; - -import android.annotation.SuppressLint; -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; -// If we don't use android.support.v7.app.AlertDialog instead of android.app.AlertDialog then the dialog will be covered by the keyboard. -import android.support.v7.app.AlertDialog; -// 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; - -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(), 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(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/DownloadFile.java b/app/src/main/java/com/stoutner/privacybrowser/DownloadFile.java deleted file mode 100644 index 1a68f71f..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/DownloadFile.java +++ /dev/null @@ -1,193 +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; - -import android.annotation.SuppressLint; -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; -// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. -import android.support.v7.app.AlertDialog; -// 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 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/DownloadImage.java b/app/src/main/java/com/stoutner/privacybrowser/DownloadImage.java deleted file mode 100644 index 3b9ee79c..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/DownloadImage.java +++ /dev/null @@ -1,162 +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; - -import android.annotation.SuppressLint; -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; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatDialogFragment; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.widget.EditText; - -// `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/EditBookmark.java b/app/src/main/java/com/stoutner/privacybrowser/EditBookmark.java deleted file mode 100644 index 3ce8204d..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/EditBookmark.java +++ /dev/null @@ -1,172 +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; - -import android.annotation.SuppressLint; -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; -// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; -// 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; - -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 = BookmarksActivity.checkedItemIds; - int selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; - - // Get a `Cursor` with the specified bookmark and move it to the first position. - Cursor bookmarkCursor = BookmarksActivity.bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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/EditBookmarkFolder.java b/app/src/main/java/com/stoutner/privacybrowser/EditBookmarkFolder.java deleted file mode 100644 index c04012b9..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/EditBookmarkFolder.java +++ /dev/null @@ -1,149 +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; - -import android.annotation.SuppressLint; -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; -// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. -import android.support.v7.app.AlertDialog; -// 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; - -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 = BookmarksActivity.checkedItemIds; - int selectedBookmarkDatabaseId = (int) selectedBookmarkLongArray[0]; - - // Get a `Cursor` with the specified bookmark and move it to the first position. - Cursor bookmarkCursor = BookmarksActivity.bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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/GuideActivity.java b/app/src/main/java/com/stoutner/privacybrowser/GuideActivity.java deleted file mode 100644 index 22b92852..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/GuideActivity.java +++ /dev/null @@ -1,109 +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; - -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; - -public class GuideActivity 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 GuideTabFragment.createTab(tab); - } - } - -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/GuideTabFragment.java b/app/src/main/java/com/stoutner/privacybrowser/GuideTabFragment.java deleted file mode 100644 index 4fa96779..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/GuideTabFragment.java +++ /dev/null @@ -1,97 +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; - -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; - -public class GuideTabFragment extends Fragment { - 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/History.java b/app/src/main/java/com/stoutner/privacybrowser/History.java deleted file mode 100644 index 0548c2b5..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/History.java +++ /dev/null @@ -1,35 +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; - -import android.graphics.Bitmap; - -// Create a `History` object. -class History { - // Create the `History` package-local variables. - Bitmap entryFavoriteIcon; - String entryUrl; - - History(Bitmap entryFavoriteIcon, String entryUrl){ - // Populate the package-local variables. - this.entryFavoriteIcon = entryFavoriteIcon; - this.entryUrl = entryUrl; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/HistoryArrayAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/HistoryArrayAdapter.java deleted file mode 100644 index 6b7f0561..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/HistoryArrayAdapter.java +++ /dev/null @@ -1,80 +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; - -import android.content.Context; -import android.graphics.Typeface; -import android.support.annotation.NonNull; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.ArrayList; - -class HistoryArrayAdapter extends ArrayAdapter { - - // `currentPage` is used in `HistoryArrayAdapter` and `getView()`. - private int currentPage; - - HistoryArrayAdapter(Context context, ArrayList historyArrayList, int currentPageId) { - // We need to call `super` from the base `ArrayAdapter`. `0` is the `textViewResourceId`. - super(context, 0, historyArrayList); - - // Store `currentPageId` in the class variable. - currentPage = currentPageId; - } - - @Override - @NonNull - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - // Inflate the view if it is `null`. - if (convertView == null) { - // `false` does not attach `url_history_item_linearlayout` to `parent`. - convertView = LayoutInflater.from(getContext()).inflate(R.layout.url_history_item_linearlayout, parent, false); - } - - // Get handles for `favoriteIconImageView` and `urlTextView`. - ImageView favoriteIconImageView = (ImageView) convertView.findViewById(R.id.history_favorite_icon_imageview); - TextView urlTextView = (TextView) convertView.findViewById(R.id.history_url_textview); - - // Get the URL history for this position. - History history = getItem(position); - - // Remove the lint warning below that `history` might be `null`. - assert history != null; - - // Set `favoriteIconImageView` and `urlTextView`. - favoriteIconImageView.setImageBitmap(history.entryFavoriteIcon); - urlTextView.setText(history.entryUrl); - - // Set the URL text for `currentPage` to be bold. - if (position == currentPage) { - urlTextView.setTypeface(Typeface.DEFAULT_BOLD); - } else { // Set the default typeface for all the other entries. - urlTextView.setTypeface(Typeface.DEFAULT); - } - - // Return the modified `convertView`. - return convertView; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java deleted file mode 100644 index 077aa49b..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java +++ /dev/null @@ -1,1564 +0,0 @@ -/** - * Copyright 2015-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; - -import android.annotation.SuppressLint; -import android.app.DialogFragment; -import android.app.DownloadManager; -import android.content.Context; -import android.content.Intent; -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.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.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -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.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 java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Map; - -// We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21. -public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener, - 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()`, and `closeFindOnPage()`. - public static ActionBar appBar; - - // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `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 `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 `ViewSslCertificate`. It is also used in `onCreate()`. - public static SslCertificate sslCertificate; - - - // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`. - private WebView mainWebView; - - // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`. - private SwipeRefreshLayout swipeRefreshLayout; - - // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, 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()`, 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; - - // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`. - private Menu mainMenu; - - // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`. - private ActionBarDrawerToggle drawerToggle; - - // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`. - private DrawerLayout drawerLayout; - - // `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; - - // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. - private EditText findOnPageEditText; - - // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`. - private InputMethodManager inputMethodManager; - - @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.main_coordinatorlayout); - - // 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. - Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar); - 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; - } - } - }); - - // Get handles for `fullScreenVideoFrameLayout`, `mainWebView`, and `find_on_page_edittext`. - final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout); - mainWebView = (WebView) findViewById(R.id.mainWebView); - findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext); - - // Update `findOnPageCountTextView`. - mainWebView.setFindListener(new WebView.FindListener() { - // Get a handle for `findOnPageCountTextView`. - 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(); - } - }); - - // Create the navigation drawer. - drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout); - // `DrawerTitle` identifies the drawer 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 item 1 and 2 and the second and third items 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, R.string.close_navigation); - - 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; - } - } - - // Update the URL in urlTextBox when the page starts to load. - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - // 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) { - 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) { - appBar.hide(); - - // Show the fullScreenVideoFrameLayout. - fullScreenVideoFrameLayout.addView(view); - fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); - - // Hide the mainWebView. - mainWebView.setVisibility(View.GONE); - - // Hide the ad if this is the free flavor. - BannerAd.hideAd(adView); - - /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen. - * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen. - * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them. - */ - view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - - // Exit full screen video - public void onHideCustomView() { - appBar.show(); - - // Show the mainWebView. - mainWebView.setVisibility(View.VISIBLE); - - // Show the ad if this is the free flavor. - BannerAd.showAd(adView); - - // Hide the fullScreenVideoFrameLayout. - fullScreenVideoFrameLayout.removeAllViews(); - fullScreenVideoFrameLayout.setVisibility(View.GONE); - } - }); - - // 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. - PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - - // Apply the settings from the shared preferences. - applySettings(); - - // 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(); - } - - // If formattedUrlString is null assign the homepage to it. - if (formattedUrlString == null) { - formattedUrlString = homepage; - } - - // Load the initial website. - 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(); - } - - // Initialize AdView for the free flavor and request an ad. If this is not the free flavor BannerAd.requestAd() does nothing. - adView = findViewById(R.id.adView); - BannerAd.requestAd(adView); - } - - - @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(); - mainWebView.reload(); - 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. - Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar); - appBarToolbar.setVisibility(View.GONE); - - // Show the Find on Page `RelativeLayout`. - LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); - 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 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.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 formData = WebViewDatabase.getInstance(this); - formData.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`. - RelativeLayout mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.mainWebViewRelativeLayout); - mainWebViewRelativeLayout.removeAllViews(); - - // Destroy the internal state of `mainWebView`. - mainWebView.destroy(); - - // 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 if this is the free flavor. - BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); - - // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor 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; - - 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` button. - menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - mainWebView.loadUrl(linkUrl, customHeaders); - return false; - } - }); - - // Add a `Cancel` button, 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` button. - 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 `Cancel` button, 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` button. - 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` button. - 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 `Cancel` button, 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` button. - 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` button. - 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 `Cancel` button, 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) { - // 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)); - - // 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); - } - - @Override - public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) { - // 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)); - - // 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 save in the the `DIRECTORY_DOWNLOADS` using `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); - } - - 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() { - final WebView mainWebView = (WebView) findViewById(R.id.mainWebView); - - // 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(); - mainWebView.pauseTimers(); - - // We need to pause the adView or it will continue to consume resources in the background on the free flavor. - BannerAd.pauseAd(adView); - - super.onPause(); - } - - @Override - public void onResume() { - super.onResume(); - - // Resume `mainWebView`. - mainWebView.resumeTimers(); - mainWebView.onResume(); - - // We need to resume the adView for the free flavor. - 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`. - LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); - findOnPageLinearLayout.setVisibility(View.GONE); - - // Show the URL app bar. - Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar); - appBarToolbar.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", "Default user agent"); - String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); - String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); - String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", ""); - String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); - String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", ""); - String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com"); - String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); - swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false); - boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true); - boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); - - // 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); - } - } - - // Apply the other settings from `sharedPreferences`. - homepage = homepageString; - 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 JavaScript disabled search. - if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string. - javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString; - } 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 = javaScriptEnabledCustomSearchString; - } else { // Use the string from the pre-built list. - javaScriptEnabledSearchURL = javaScriptEnabledSearchString; - } - - // Set Do Not Track status. - if (doNotTrackEnabled) { - customHeaders.put("DNT", "1"); - } else { - customHeaders.remove("DNT"); - } - - // Set Orbot proxy status. - if (proxyThroughOrbot) { - // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed. - OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118"); - } else { // Reset the proxy to default. The host is `""` and the port is `"0"`. - OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0"); - } - } - - 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/MoveToFolder.java b/app/src/main/java/com/stoutner/privacybrowser/MoveToFolder.java deleted file mode 100644 index 60f7707f..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/MoveToFolder.java +++ /dev/null @@ -1,267 +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; - -import android.annotation.SuppressLint; -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; -// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. -import android.support.v4.content.ContextCompat; -// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. -import android.support.v7.app.AlertDialog; -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 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 (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.bookmarksDatabaseHandler.isFolder(databaseIdInt)) { - // Get the name of the selected folder. - String folderName = BookmarksActivity.bookmarksDatabaseHandler.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.bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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_grey_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 = {BookmarksDatabaseHandler._ID, BookmarksDatabaseHandler.BOOKMARK_NAME, BookmarksDatabaseHandler.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.bookmarksDatabaseHandler.isFolder(databaseIdInt)) { - // Get the name of the selected folder. - String folderName = BookmarksActivity.bookmarksDatabaseHandler.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.bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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.bookmarksDatabaseHandler.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(BookmarksDatabaseHandler.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/OrbotProxyHelper.java b/app/src/main/java/com/stoutner/privacybrowser/OrbotProxyHelper.java deleted file mode 100644 index 58084ad2..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/OrbotProxyHelper.java +++ /dev/null @@ -1,101 +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; - -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Proxy; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.util.ArrayMap; -import android.util.Log; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -class OrbotProxyHelper { - static void setProxy(Context privacyBrowserContext, Activity parentActivity, String proxyHost, String proxyPort) { - // Set the proxy values - System.setProperty("http.proxyHost", proxyHost); - System.setProperty("http.proxyPort", proxyPort); - System.setProperty("https.proxyHost", proxyHost); - System.setProperty("https.proxyPort", proxyPort); - - // Use reflection to apply the new proxy values. - try { - Class applicationClass = Class.forName("android.app.Application"); - Field mLoadedApkField = applicationClass.getDeclaredField("mLoadedApk"); - // `setAccessible(true)` allows us to change the value of `mLoadedApkField`. - mLoadedApkField.setAccessible(true); - Object mLoadedApkObject = mLoadedApkField.get(privacyBrowserContext); - - Class loadedApkClass = Class.forName("android.app.LoadedApk"); - Field mReceiversField = loadedApkClass.getDeclaredField("mReceivers"); - // `setAccessible(true)` allows us to change the value of `mReceiversField`. - mReceiversField.setAccessible(true); - - ArrayMap receivers = (ArrayMap) mReceiversField.get(mLoadedApkObject); - - for (Object receiverMap : receivers.values()) { - for (Object receiver : ((ArrayMap) receiverMap).keySet()) { - // We have to use `Class`, which is an `unbounded wildcard parameterized type`, instead of `Class`, which is a `raw type`, or `receiveClass.getDeclaredMethod` below will produce an error. - Class receiverClass = receiver.getClass(); - if (receiverClass.getName().contains("ProxyChangeListener")) { - Method onReceiveMethod = receiverClass.getDeclaredMethod("onReceive", Context.class, Intent.class); - Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); - onReceiveMethod.invoke(receiver, privacyBrowserContext, intent); - } - } - } - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) { - Log.d("enableProxyThroughOrbot", "Exception: " + exception); - } - - if (proxyPort.equals("8118")) { // Orbot proxy was turned on. - // Set the `appBar` background to be light blue if Orbot proxy support is enabled. - 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); - } catch (PackageManager.NameNotFoundException exception){ // If an exception is thrown, Orbot is not installed. - // Build an `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(parentActivity, R.style.LightAlertDialog); - dialogBuilder.setMessage(R.string.orbot_proxy_not_installed); - dialogBuilder.setPositiveButton(R.string.close, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Do nothing. The `AlertDialog` will close automatically. - } - }); - - // Convert `dialogBuilder` to `alertDialog` and display it on the screen. - AlertDialog alertDialog = dialogBuilder.create(); - alertDialog.show(); - } - } else { // Otherwise set the default grey `appBar` background. - MainWebViewActivity.appBar.setBackgroundDrawable(ContextCompat.getDrawable(privacyBrowserContext, R.color.grey_100)); - } - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/SettingsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/SettingsActivity.java deleted file mode 100644 index 39051382..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/SettingsActivity.java +++ /dev/null @@ -1,36 +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; - -import android.os.Bundle; -import android.preference.PreferenceFragment; -import android.support.v7.app.AppCompatActivity; - -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/SettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/SettingsFragment.java deleted file mode 100644 index 5d73e193..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/SettingsFragment.java +++ /dev/null @@ -1,249 +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; - -import android.annotation.SuppressLint; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.PreferenceFragment; -import android.view.LayoutInflater; -import android.view.View; -import android.webkit.WebView; - -public class SettingsFragment extends PreferenceFragment { - private SharedPreferences.OnSharedPreferenceChangeListener preferencesListener; - private SharedPreferences savedPreferences; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.preferences); - - // Initialize savedPreferences. - savedPreferences = getPreferenceScreen().getSharedPreferences(); - - // Allow the user to access "dom_storage_enabled" if "javascript_enabled" is enabled. The default is false. - final Preference domStorageEnabled = findPreference("dom_storage_enabled"); - domStorageEnabled.setEnabled(savedPreferences.getBoolean("javascript_enabled", false)); - - // Allow the user to access "third_party_cookies_enabled" if "first_party_cookies_enabled" is enabled. The default is false. - final Preference thirdPartyCookiesEnabled = findPreference("third_party_cookies_enabled"); - thirdPartyCookiesEnabled.setEnabled(savedPreferences.getBoolean("first_party_cookies_enabled", false)); - - - // We need an inflated `WebView` to get the default user agent. - LayoutInflater inflater = getActivity().getLayoutInflater(); - // `@SuppressLint("InflateParams")` removes the warning about using `null` as the `ViewGroup`, which in this case makes sense because we don't want to display `bare_webview` on the screen. - // `false` does not attach the view to the root. - @SuppressLint("InflateParams") View bareWebViewLayout = inflater.inflate(R.layout.bare_webview, null, false); - final WebView bareWebView = (WebView) bareWebViewLayout.findViewById(R.id.bare_webview); - - // Set the current user-agent as the summary text for the "user_agent" preference when the preference screen is loaded. - final Preference userAgentPreference = findPreference("user_agent"); - switch (savedPreferences.getString("user_agent", "Default user agent")) { - case "Default user agent": - // Get the user agent text from the webview (which changes based on the version of Android and WebView installed). - userAgentPreference.setSummary(bareWebView.getSettings().getUserAgentString()); - break; - - case "Custom user agent": - // We can't use the string from the array because it is referenced in code and can't be translated. - userAgentPreference.setSummary(R.string.custom_user_agent); - break; - - default: - // Display the user agent from the preference as the summary text. - userAgentPreference.setSummary(savedPreferences.getString("user_agent", "Default user agent")); - break; - } - - // Set the summary text for "custom_user_agent" (the default is "PrivacyBrowser/1.0") and enable it if "user_agent" it set to "Custom user agent". - final Preference customUserAgent = findPreference("custom_user_agent"); - customUserAgent.setSummary(savedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0")); - customUserAgent.setEnabled(userAgentPreference.getSummary().equals("Custom user agent")); - - - // Set the JavaScript-disabled search URL as the summary text for the JavaScript-disabled search preference when the preference screen is loaded. - // The default is "https://duckduckgo.com/html/?q=". - final Preference javaScriptDisabledSearchPreference = findPreference("javascript_disabled_search"); - String javaScriptDisabledSearchString = savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); - if (javaScriptDisabledSearchString.equals("Custom URL")) { - // If set to "Custom URL", use R.string.custom_url, which will be translated, instead of the array value, which will not. - javaScriptDisabledSearchPreference.setSummary(R.string.custom_url); - } else { - // Set the array value as the summary text. - javaScriptDisabledSearchPreference.setSummary(javaScriptDisabledSearchString); - } - - // Set the summary text for "javascript_disabled_search_custom_url" (the default is "") and enable it if "javascript_disabled_search" is set to "Custom URL". - final Preference javaScriptDisabledSearchCustomURLPreference = findPreference("javascript_disabled_search_custom_url"); - javaScriptDisabledSearchCustomURLPreference.setSummary(savedPreferences.getString("javascript_disabled_search_custom_url", "")); - javaScriptDisabledSearchCustomURLPreference.setEnabled(javaScriptDisabledSearchString.equals("Custom URL")); - - - // Set the JavaScript-enabled searchURL as the summary text for the JavaScript-enabled search preference when the preference screen is loaded. - // The default is "https://duckduckgo.com/?q=". - final Preference javaScriptEnabledSearchPreference = findPreference("javascript_enabled_search"); - String javaScriptEnabledSearchString = savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); - if (javaScriptEnabledSearchString.equals("Custom URL")) { - // If set to "Custom URL", use R.string.custom_url, which will be translated, instead of the array value, which will not. - javaScriptEnabledSearchPreference.setSummary(R.string.custom_url); - } else { - // Set the array value as the summary text. - javaScriptEnabledSearchPreference.setSummary(javaScriptEnabledSearchString); - } - - // Set the summary text for "javascript_enabled_search_custom_url" (the default is "") and enable it if "javascript_enabled_search" is set to "Custom URL". - final Preference javaScriptEnabledSearchCustomURLPreference = findPreference("javascript_enabled_search_custom_url"); - javaScriptEnabledSearchCustomURLPreference.setSummary(savedPreferences.getString("javascript_enabled_search_custom_url", "")); - javaScriptEnabledSearchCustomURLPreference.setEnabled(javaScriptEnabledSearchString.equals("Custom URL")); - - - // Set the homepage URL as the summary text for the `Homepage` preference when the preference screen is loaded. The default is `https://www.duckduckgo.com`. - final Preference homepagePreference = findPreference("homepage"); - homepagePreference.setSummary(savedPreferences.getString("homepage", "https://www.duckduckgo.com")); - - // Set the default font size as the summary text for the `Default Font Size` preference when the preference screen is loaded. The default is `100`. - final Preference defaultFontSizePreference = findPreference("default_font_size"); - String defaultFontSizeString = savedPreferences.getString("default_font_size", "100"); - defaultFontSizePreference.setSummary(defaultFontSizeString + "%%"); - - - // Listen for preference changes. - preferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() { - @Override - // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. We know. - @SuppressLint("SetJavaScriptEnabled") - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - - switch (key) { - case "javascript_enabled": - // Toggle the state of the `dom_storage_enabled` preference. The default is `false`. - final Preference domStorageEnabled = findPreference("dom_storage_enabled"); - domStorageEnabled.setEnabled(sharedPreferences.getBoolean("javascript_enabled", false)); - break; - - case "first_party_cookies_enabled": - // Toggle the state of the `third_party_cookies_enabled` preference. The default is `false`. - final Preference thirdPartyCookiesEnabled = findPreference("third_party_cookies_enabled"); - thirdPartyCookiesEnabled.setEnabled(sharedPreferences.getBoolean("first_party_cookies_enabled", false)); - break; - - case "user_agent": - String userAgentString = sharedPreferences.getString("user_agent", "Default user agent"); - - switch (userAgentString) { - case "Default user agent": - // Display the user agent as the summary text for `userAgentPreference`, and disable `customUserAgent`. - userAgentPreference.setSummary(bareWebView.getSettings().getUserAgentString()); - customUserAgent.setEnabled(false); - break; - - case "Custom user agent": - // Display "Custom user agent" as the summary text for userAgentPreference, and enable customUserAgent. - userAgentPreference.setSummary(R.string.custom_user_agent); - customUserAgent.setEnabled(true); - break; - - default: - // Display the user agent as the summary text for userAgentPreference, and disable customUserAgent. - userAgentPreference.setSummary(sharedPreferences.getString("user_agent", "Default user agent")); - customUserAgent.setEnabled(false); - break; - } - break; - - case "custom_user_agent": - // Set the new custom user agent as the summary text for "custom_user_agent". The default is "PrivacyBrowser/1.0". - customUserAgent.setSummary(sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0")); - break; - - case "javascript_disabled_search": - String newJavaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); - if (newJavaScriptDisabledSearchString.equals("Custom URL")) { // Set the summary text to `R.string.custom_url`, which is translated. - javaScriptDisabledSearchPreference.setSummary(R.string.custom_url); - } else { // Set the new search URL as the summary text for the JavaScript-disabled search preference. - javaScriptDisabledSearchPreference.setSummary(newJavaScriptDisabledSearchString); - } - - // Enable or disable javaScriptDisabledSearchCustomURLPreference. - javaScriptDisabledSearchCustomURLPreference.setEnabled(newJavaScriptDisabledSearchString.equals("Custom URL")); - break; - - case "javascript_disabled_search_custom_url": - // Set the new custom search URL as the summary text for `javascript_disabled_search_custom_url`. The default is `""`. - javaScriptDisabledSearchCustomURLPreference.setSummary(sharedPreferences.getString("javascript_disabled_search_custom_url", "")); - break; - - case "javascript_enabled_search": - String newJavaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); - if (newJavaScriptEnabledSearchString.equals("Custom URL")) { // Set the summary text to `R.string.custom_url`, which is translated. - javaScriptEnabledSearchPreference.setSummary(R.string.custom_url); - } else { // Set the new search URL as the summary text for the JavaScript-enabled search preference.. - javaScriptEnabledSearchPreference.setSummary(newJavaScriptEnabledSearchString); - } - - // Enable or disable javaScriptEnabledSearchCustomURLPreference. - javaScriptEnabledSearchCustomURLPreference.setEnabled(newJavaScriptEnabledSearchString.equals("Custom URL")); - break; - - case "javascript_enabled_search_custom_url": - // Set the new custom search URL as the summary text for `javascript_enabled_search_custom_url`. The default is `""`. - javaScriptEnabledSearchCustomURLPreference.setSummary(sharedPreferences.getString("javascript_enabled_search_custom_url", "")); - break; - - case "homepage": - // Set the new homepage URL as the summary text for the Homepage preference. The default is `https://www.duckduckgo.com`. - homepagePreference.setSummary(sharedPreferences.getString("homepage", "https://www.duckduckgo.com")); - break; - - case "default_font_size": - // Get the default font size as a string. The default is `100`. - String newDefaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); - - // Update the summary text of `default_font_size`. - defaultFontSizePreference.setSummary(newDefaultFontSizeString + "%%"); - - default: - // If no match, do nothing. - break; - } - } - }; - - // Register the listener. - savedPreferences.registerOnSharedPreferenceChangeListener(preferencesListener); - } - - // It is necessary to re-register the listener on every resume or it will randomly stop working because apps can be paused and resumed at any time - // even while running in the foreground. - @Override - public void onPause() { - super.onPause(); - savedPreferences.unregisterOnSharedPreferenceChangeListener(preferencesListener); - } - - @Override - public void onResume() { - super.onResume(); - savedPreferences.registerOnSharedPreferenceChangeListener(preferencesListener); - } -} diff --git a/app/src/main/java/com/stoutner/privacybrowser/SslCertificateError.java b/app/src/main/java/com/stoutner/privacybrowser/SslCertificateError.java deleted file mode 100644 index 388a09bd..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/SslCertificateError.java +++ /dev/null @@ -1,249 +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; - -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; -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 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", startDateForBundle.toString()); - argumentsBundle.putString("EndDate", endDateForBundle.toString()); - - // 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/UrlHistory.java b/app/src/main/java/com/stoutner/privacybrowser/UrlHistory.java deleted file mode 100644 index ec0b45e4..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/UrlHistory.java +++ /dev/null @@ -1,252 +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; - -import android.annotation.SuppressLint; -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; -// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. -import android.support.annotation.NonNull; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -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 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/ViewSslCertificate.java b/app/src/main/java/com/stoutner/privacybrowser/ViewSslCertificate.java deleted file mode 100644 index 76b6abe6..00000000 --- a/app/src/main/java/com/stoutner/privacybrowser/ViewSslCertificate.java +++ /dev/null @@ -1,152 +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; - -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 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(), 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 + startDate.toString()); - SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder(endDateLabel + endDate.toString()); - - // 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/activities/About.java b/app/src/main/java/com/stoutner/privacybrowser/activities/About.java new file mode 100644 index 00000000..10ec431a --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/About.java @@ -0,0 +1,106 @@ +/** + * 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.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/Bookmarks.java b/app/src/main/java/com/stoutner/privacybrowser/activities/Bookmarks.java new file mode 100644 index 00000000..78cb806e --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/Bookmarks.java @@ -0,0 +1,884 @@ +/** + * 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.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 `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_listview); + + // Set currentFolder to the home folder, which is null 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` 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; + + // Sometimes Android forgets to close the contextual app bar when all the items are deselected. + if (numberOfSelectedBookmarks == 0) { + mode.finish(); + } + + // List the number of selected bookmarks in the subtitle. + mode.setSubtitle(numberOfSelectedBookmarks + " " + getString(R.string.selected)); + + if (numberOfSelectedBookmarks == 1) { + // Show the `Move Up`, `Move Down`, and `Edit` option only if 1 bookmark is selected. + 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 { // Hide the MenuItems because more than one bookmark is selected. + 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_rename_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_LONG).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 new file mode 100644 index 00000000..e7795fbb --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/BookmarksDatabaseView.java @@ -0,0 +1,150 @@ +/** + * 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.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_grey)); + bookmarkParentFolderTextView.setText(R.string.home_folder); + bookmarkParentFolderTextView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.grey_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/Guide.java b/app/src/main/java/com/stoutner/privacybrowser/activities/Guide.java new file mode 100644 index 00000000..2861f0e3 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/Guide.java @@ -0,0 +1,112 @@ +/** + * 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/MainWebView.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java new file mode 100644 index 00000000..5fdd814e --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java @@ -0,0 +1,1574 @@ +/** + * Copyright 2015-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.annotation.SuppressLint; +import android.app.DialogFragment; +import android.app.DownloadManager; +import android.content.Context; +import android.content.Intent; +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.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.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +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.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.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.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +// 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()`, and `closeFindOnPage()`. + 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; + + + // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`. + private WebView mainWebView; + + // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`. + private SwipeRefreshLayout swipeRefreshLayout; + + // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, 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()`, 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; + + // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`. + private Menu mainMenu; + + // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`. + private ActionBarDrawerToggle drawerToggle; + + // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`. + private DrawerLayout drawerLayout; + + // `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; + + // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. + private EditText findOnPageEditText; + + // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`. + private InputMethodManager inputMethodManager; + + @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.main_coordinatorlayout); + + // 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. + Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar); + 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; + } + } + }); + + // Get handles for `fullScreenVideoFrameLayout`, `mainWebView`, and `find_on_page_edittext`. + final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout); + mainWebView = (WebView) findViewById(R.id.mainWebView); + findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext); + + // Update `findOnPageCountTextView`. + mainWebView.setFindListener(new WebView.FindListener() { + // Get a handle for `findOnPageCountTextView`. + 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(); + } + }); + + // Create the navigation drawer. + drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout); + // `DrawerTitle` identifies the drawer 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 item 1 and 2 and the second and third items 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, R.string.close_navigation); + + 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; + } + } + + // Update the URL in urlTextBox when the page starts to load. + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + // 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) { + 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) { + appBar.hide(); + + // Show the fullScreenVideoFrameLayout. + fullScreenVideoFrameLayout.addView(view); + fullScreenVideoFrameLayout.setVisibility(View.VISIBLE); + + // Hide the mainWebView. + mainWebView.setVisibility(View.GONE); + + // Hide the ad if this is the free flavor. + BannerAd.hideAd(adView); + + /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen. + * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen. + * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them. + */ + view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + } + + // Exit full screen video + public void onHideCustomView() { + appBar.show(); + + // Show the mainWebView. + mainWebView.setVisibility(View.VISIBLE); + + // Show the ad if this is the free flavor. + BannerAd.showAd(adView); + + // Hide the fullScreenVideoFrameLayout. + fullScreenVideoFrameLayout.removeAllViews(); + fullScreenVideoFrameLayout.setVisibility(View.GONE); + } + }); + + // 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. + PreferenceManager.setDefaultValues(this, R.xml.preferences, false); + + // Apply the settings from the shared preferences. + applySettings(); + + // 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(); + } + + // If formattedUrlString is null assign the homepage to it. + if (formattedUrlString == null) { + formattedUrlString = homepage; + } + + // Load the initial website. + 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(); + } + + // Initialize AdView for the free flavor and request an ad. If this is not the free flavor BannerAd.requestAd() does nothing. + adView = findViewById(R.id.adView); + BannerAd.requestAd(adView); + } + + + @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(); + mainWebView.reload(); + 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. + Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar); + appBarToolbar.setVisibility(View.GONE); + + // Show the Find on Page `RelativeLayout`. + LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); + 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.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 formData = WebViewDatabase.getInstance(this); + formData.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`. + RelativeLayout mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.mainWebViewRelativeLayout); + mainWebViewRelativeLayout.removeAllViews(); + + // Destroy the internal state of `mainWebView`. + mainWebView.destroy(); + + // 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 if this is the free flavor. + BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id)); + + // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor 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; + + 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` button. + menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + mainWebView.loadUrl(linkUrl, customHeaders); + return false; + } + }); + + // Add a `Cancel` button, 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` button. + 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 `Cancel` button, 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` button. + 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` button. + 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 `Cancel` button, 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` button. + 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` button. + 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 `Cancel` button, 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) { + // 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)); + + // 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); + } + + @Override + public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) { + // 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)); + + // 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 save in the the `DIRECTORY_DOWNLOADS` using `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); + } + + 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() { + final WebView mainWebView = (WebView) findViewById(R.id.mainWebView); + + // 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(); + mainWebView.pauseTimers(); + + // We need to pause the adView or it will continue to consume resources in the background on the free flavor. + BannerAd.pauseAd(adView); + + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + + // Resume `mainWebView`. + mainWebView.resumeTimers(); + mainWebView.onResume(); + + // We need to resume the adView for the free flavor. + 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`. + LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); + findOnPageLinearLayout.setVisibility(View.GONE); + + // Show the URL app bar. + Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar); + appBarToolbar.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", "Default user agent"); + String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"); + String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); + String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", ""); + String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); + String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", ""); + String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com"); + String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); + swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false); + boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true); + boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false); + + // 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); + } + } + + // Apply the other settings from `sharedPreferences`. + homepage = homepageString; + 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 JavaScript disabled search. + if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string. + javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString; + } 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 = javaScriptEnabledCustomSearchString; + } else { // Use the string from the pre-built list. + javaScriptEnabledSearchURL = javaScriptEnabledSearchString; + } + + // Set Do Not Track status. + if (doNotTrackEnabled) { + customHeaders.put("DNT", "1"); + } else { + customHeaders.remove("DNT"); + } + + // Set Orbot proxy status. + if (proxyThroughOrbot) { + // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed. + OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118"); + } else { // Reset the proxy to default. The host is `""` and the port is `"0"`. + OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0"); + } + } + + 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 new file mode 100644 index 00000000..027482c7 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/Settings.java @@ -0,0 +1,38 @@ +/** + * 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/adapters/HistoryArrayAdapter.java b/app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.java new file mode 100644 index 00000000..1135638c --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/adapters/HistoryArrayAdapter.java @@ -0,0 +1,83 @@ +/** + * 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.adapters; + +import android.content.Context; +import android.graphics.Typeface; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.definitions.History; + +import java.util.ArrayList; + +public class HistoryArrayAdapter extends ArrayAdapter { + + // `currentPage` is used in `HistoryArrayAdapter` and `getView()`. + private int currentPage; + + public HistoryArrayAdapter(Context context, ArrayList historyArrayList, int currentPageId) { + // We need to call `super` from the base `ArrayAdapter`. `0` is the `textViewResourceId`. + super(context, 0, historyArrayList); + + // Store `currentPageId` in the class variable. + currentPage = currentPageId; + } + + @Override + @NonNull + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + // Inflate the view if it is `null`. + if (convertView == null) { + // `false` does not attach `url_history_item_linearlayout` to `parent`. + convertView = LayoutInflater.from(getContext()).inflate(R.layout.url_history_item_linearlayout, parent, false); + } + + // Get handles for `favoriteIconImageView` and `urlTextView`. + ImageView favoriteIconImageView = (ImageView) convertView.findViewById(R.id.history_favorite_icon_imageview); + TextView urlTextView = (TextView) convertView.findViewById(R.id.history_url_textview); + + // Get the URL history for this position. + History history = getItem(position); + + // Remove the lint warning below that `history` might be `null`. + assert history != null; + + // Set `favoriteIconImageView` and `urlTextView`. + favoriteIconImageView.setImageBitmap(history.entryFavoriteIcon); + urlTextView.setText(history.entryUrl); + + // Set the URL text for `currentPage` to be bold. + if (position == currentPage) { + urlTextView.setTypeface(Typeface.DEFAULT_BOLD); + } else { // Set the default typeface for all the other entries. + urlTextView.setTypeface(Typeface.DEFAULT); + } + + // Return the modified `convertView`. + return convertView; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java b/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java new file mode 100644 index 00000000..e5804218 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/definitions/History.java @@ -0,0 +1,35 @@ +/** + * 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.definitions; + +import android.graphics.Bitmap; + +// Create a `History` object. +public class History { + // Create the `History` package-local variables. + public Bitmap entryFavoriteIcon; + public String entryUrl; + + public History(Bitmap entryFavoriteIcon, String entryUrl){ + // Populate the package-local variables. + this.entryFavoriteIcon = entryFavoriteIcon; + this.entryUrl = entryUrl; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmark.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmark.java new file mode 100644 index 00000000..77c58607 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmark.java @@ -0,0 +1,152 @@ +/** + * 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.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; +// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. +import android.support.v7.app.AlertDialog; +// 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 `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 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 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 `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" button, select the PositiveButton "Create". + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { + // 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/CreateBookmarkFolder.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolder.java new file mode 100644 index 00000000..e3afd11b --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateBookmarkFolder.java @@ -0,0 +1,129 @@ +/** + * 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.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. +import android.support.v7.app.AlertDialog; +// 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/CreateHomeScreenShortcut.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcut.java new file mode 100644 index 00000000..d55f7ff7 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/CreateHomeScreenShortcut.java @@ -0,0 +1,133 @@ +/** + * Copyright 2015-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.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; +// If we don't use android.support.v7.app.AlertDialog instead of android.app.AlertDialog then the dialog will be covered by the keyboard. +import android.support.v7.app.AlertDialog; +// 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/DownloadFile.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFile.java new file mode 100644 index 00000000..fe2048af --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadFile.java @@ -0,0 +1,195 @@ +/** + * 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.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. +import android.support.v7.app.AlertDialog; +// 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/DownloadImage.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImage.java new file mode 100644 index 00000000..ceb0d624 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/DownloadImage.java @@ -0,0 +1,164 @@ +/** + * 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.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatDialogFragment; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; + +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/EditBookmark.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmark.java new file mode 100644 index 00000000..a02a4deb --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmark.java @@ -0,0 +1,177 @@ +/** + * 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.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; +// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +// 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/EditBookmarkFolder.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolder.java new file mode 100644 index 00000000..74bd8903 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/EditBookmarkFolder.java @@ -0,0 +1,154 @@ +/** + * 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.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; +// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. +import android.support.v7.app.AlertDialog; +// 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/MoveToFolder.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolder.java new file mode 100644 index 00000000..e2e2f88f --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/MoveToFolder.java @@ -0,0 +1,271 @@ +/** + * 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.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; +// If we don't use `android.support.v7.app.AlertDialog` instead of `android.app.AlertDialog` then the dialog will be covered by the keyboard. +import android.support.v4.content.ContextCompat; +// We have to use `AppCompatDialogFragment` instead of `DialogFragment` or an error is produced on API <=22. +import android.support.v7.app.AlertDialog; +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_grey_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/SslCertificateError.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateError.java new file mode 100644 index 00000000..1e956162 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/SslCertificateError.java @@ -0,0 +1,251 @@ +/** + * 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.net.http.SslCertificate; +import android.net.http.SslError; +import android.os.Bundle; +import android.support.annotation.NonNull; +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.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", startDateForBundle.toString()); + argumentsBundle.putString("EndDate", endDateForBundle.toString()); + + // 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/UrlHistory.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistory.java new file mode 100644 index 00000000..46a7bbb5 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/UrlHistory.java @@ -0,0 +1,256 @@ +/** + * 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.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; +// `android.support.v7.app.AlertDialog` uses more of the horizontal screen real estate versus `android.app.AlertDialog's` smaller width. +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; +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/ViewSslCertificate.java b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificate.java new file mode 100644 index 00000000..d977cbed --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/dialogs/ViewSslCertificate.java @@ -0,0 +1,155 @@ +/** + * 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.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.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 + startDate.toString()); + SpannableStringBuilder endDateStringBuilder = new SpannableStringBuilder(endDateLabel + endDate.toString()); + + // 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 new file mode 100644 index 00000000..7baff969 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/AboutTab.java @@ -0,0 +1,208 @@ +/** + * 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(buildStringBuilder); + 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/GuideTab.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTab.java new file mode 100644 index 00000000..8857aca4 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/GuideTab.java @@ -0,0 +1,99 @@ +/** + * 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/SettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java new file mode 100644 index 00000000..2149df50 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java @@ -0,0 +1,251 @@ +/** + * 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.annotation.SuppressLint; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.WebView; + +import com.stoutner.privacybrowser.R; + +public class SettingsFragment extends PreferenceFragment { + private SharedPreferences.OnSharedPreferenceChangeListener preferencesListener; + private SharedPreferences savedPreferences; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + + // Initialize savedPreferences. + savedPreferences = getPreferenceScreen().getSharedPreferences(); + + // Allow the user to access "dom_storage_enabled" if "javascript_enabled" is enabled. The default is false. + final Preference domStorageEnabled = findPreference("dom_storage_enabled"); + domStorageEnabled.setEnabled(savedPreferences.getBoolean("javascript_enabled", false)); + + // Allow the user to access "third_party_cookies_enabled" if "first_party_cookies_enabled" is enabled. The default is false. + final Preference thirdPartyCookiesEnabled = findPreference("third_party_cookies_enabled"); + thirdPartyCookiesEnabled.setEnabled(savedPreferences.getBoolean("first_party_cookies_enabled", false)); + + + // We need an inflated `WebView` to get the default user agent. + LayoutInflater inflater = getActivity().getLayoutInflater(); + // `@SuppressLint("InflateParams")` removes the warning about using `null` as the `ViewGroup`, which in this case makes sense because we don't want to display `bare_webview` on the screen. + // `false` does not attach the view to the root. + @SuppressLint("InflateParams") View bareWebViewLayout = inflater.inflate(R.layout.bare_webview, null, false); + final WebView bareWebView = (WebView) bareWebViewLayout.findViewById(R.id.bare_webview); + + // Set the current user-agent as the summary text for the "user_agent" preference when the preference screen is loaded. + final Preference userAgentPreference = findPreference("user_agent"); + switch (savedPreferences.getString("user_agent", "Default user agent")) { + case "Default user agent": + // Get the user agent text from the webview (which changes based on the version of Android and WebView installed). + userAgentPreference.setSummary(bareWebView.getSettings().getUserAgentString()); + break; + + case "Custom user agent": + // We can't use the string from the array because it is referenced in code and can't be translated. + userAgentPreference.setSummary(R.string.custom_user_agent); + break; + + default: + // Display the user agent from the preference as the summary text. + userAgentPreference.setSummary(savedPreferences.getString("user_agent", "Default user agent")); + break; + } + + // Set the summary text for "custom_user_agent" (the default is "PrivacyBrowser/1.0") and enable it if "user_agent" it set to "Custom user agent". + final Preference customUserAgent = findPreference("custom_user_agent"); + customUserAgent.setSummary(savedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0")); + customUserAgent.setEnabled(userAgentPreference.getSummary().equals("Custom user agent")); + + + // Set the JavaScript-disabled search URL as the summary text for the JavaScript-disabled search preference when the preference screen is loaded. + // The default is "https://duckduckgo.com/html/?q=". + final Preference javaScriptDisabledSearchPreference = findPreference("javascript_disabled_search"); + String javaScriptDisabledSearchString = savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); + if (javaScriptDisabledSearchString.equals("Custom URL")) { + // If set to "Custom URL", use R.string.custom_url, which will be translated, instead of the array value, which will not. + javaScriptDisabledSearchPreference.setSummary(R.string.custom_url); + } else { + // Set the array value as the summary text. + javaScriptDisabledSearchPreference.setSummary(javaScriptDisabledSearchString); + } + + // Set the summary text for "javascript_disabled_search_custom_url" (the default is "") and enable it if "javascript_disabled_search" is set to "Custom URL". + final Preference javaScriptDisabledSearchCustomURLPreference = findPreference("javascript_disabled_search_custom_url"); + javaScriptDisabledSearchCustomURLPreference.setSummary(savedPreferences.getString("javascript_disabled_search_custom_url", "")); + javaScriptDisabledSearchCustomURLPreference.setEnabled(javaScriptDisabledSearchString.equals("Custom URL")); + + + // Set the JavaScript-enabled searchURL as the summary text for the JavaScript-enabled search preference when the preference screen is loaded. + // The default is "https://duckduckgo.com/?q=". + final Preference javaScriptEnabledSearchPreference = findPreference("javascript_enabled_search"); + String javaScriptEnabledSearchString = savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); + if (javaScriptEnabledSearchString.equals("Custom URL")) { + // If set to "Custom URL", use R.string.custom_url, which will be translated, instead of the array value, which will not. + javaScriptEnabledSearchPreference.setSummary(R.string.custom_url); + } else { + // Set the array value as the summary text. + javaScriptEnabledSearchPreference.setSummary(javaScriptEnabledSearchString); + } + + // Set the summary text for "javascript_enabled_search_custom_url" (the default is "") and enable it if "javascript_enabled_search" is set to "Custom URL". + final Preference javaScriptEnabledSearchCustomURLPreference = findPreference("javascript_enabled_search_custom_url"); + javaScriptEnabledSearchCustomURLPreference.setSummary(savedPreferences.getString("javascript_enabled_search_custom_url", "")); + javaScriptEnabledSearchCustomURLPreference.setEnabled(javaScriptEnabledSearchString.equals("Custom URL")); + + + // Set the homepage URL as the summary text for the `Homepage` preference when the preference screen is loaded. The default is `https://www.duckduckgo.com`. + final Preference homepagePreference = findPreference("homepage"); + homepagePreference.setSummary(savedPreferences.getString("homepage", "https://www.duckduckgo.com")); + + // Set the default font size as the summary text for the `Default Font Size` preference when the preference screen is loaded. The default is `100`. + final Preference defaultFontSizePreference = findPreference("default_font_size"); + String defaultFontSizeString = savedPreferences.getString("default_font_size", "100"); + defaultFontSizePreference.setSummary(defaultFontSizeString + "%%"); + + + // Listen for preference changes. + preferencesListener = new SharedPreferences.OnSharedPreferenceChangeListener() { + @Override + // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. We know. + @SuppressLint("SetJavaScriptEnabled") + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + + switch (key) { + case "javascript_enabled": + // Toggle the state of the `dom_storage_enabled` preference. The default is `false`. + final Preference domStorageEnabled = findPreference("dom_storage_enabled"); + domStorageEnabled.setEnabled(sharedPreferences.getBoolean("javascript_enabled", false)); + break; + + case "first_party_cookies_enabled": + // Toggle the state of the `third_party_cookies_enabled` preference. The default is `false`. + final Preference thirdPartyCookiesEnabled = findPreference("third_party_cookies_enabled"); + thirdPartyCookiesEnabled.setEnabled(sharedPreferences.getBoolean("first_party_cookies_enabled", false)); + break; + + case "user_agent": + String userAgentString = sharedPreferences.getString("user_agent", "Default user agent"); + + switch (userAgentString) { + case "Default user agent": + // Display the user agent as the summary text for `userAgentPreference`, and disable `customUserAgent`. + userAgentPreference.setSummary(bareWebView.getSettings().getUserAgentString()); + customUserAgent.setEnabled(false); + break; + + case "Custom user agent": + // Display "Custom user agent" as the summary text for userAgentPreference, and enable customUserAgent. + userAgentPreference.setSummary(R.string.custom_user_agent); + customUserAgent.setEnabled(true); + break; + + default: + // Display the user agent as the summary text for userAgentPreference, and disable customUserAgent. + userAgentPreference.setSummary(sharedPreferences.getString("user_agent", "Default user agent")); + customUserAgent.setEnabled(false); + break; + } + break; + + case "custom_user_agent": + // Set the new custom user agent as the summary text for "custom_user_agent". The default is "PrivacyBrowser/1.0". + customUserAgent.setSummary(sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0")); + break; + + case "javascript_disabled_search": + String newJavaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q="); + if (newJavaScriptDisabledSearchString.equals("Custom URL")) { // Set the summary text to `R.string.custom_url`, which is translated. + javaScriptDisabledSearchPreference.setSummary(R.string.custom_url); + } else { // Set the new search URL as the summary text for the JavaScript-disabled search preference. + javaScriptDisabledSearchPreference.setSummary(newJavaScriptDisabledSearchString); + } + + // Enable or disable javaScriptDisabledSearchCustomURLPreference. + javaScriptDisabledSearchCustomURLPreference.setEnabled(newJavaScriptDisabledSearchString.equals("Custom URL")); + break; + + case "javascript_disabled_search_custom_url": + // Set the new custom search URL as the summary text for `javascript_disabled_search_custom_url`. The default is `""`. + javaScriptDisabledSearchCustomURLPreference.setSummary(sharedPreferences.getString("javascript_disabled_search_custom_url", "")); + break; + + case "javascript_enabled_search": + String newJavaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q="); + if (newJavaScriptEnabledSearchString.equals("Custom URL")) { // Set the summary text to `R.string.custom_url`, which is translated. + javaScriptEnabledSearchPreference.setSummary(R.string.custom_url); + } else { // Set the new search URL as the summary text for the JavaScript-enabled search preference.. + javaScriptEnabledSearchPreference.setSummary(newJavaScriptEnabledSearchString); + } + + // Enable or disable javaScriptEnabledSearchCustomURLPreference. + javaScriptEnabledSearchCustomURLPreference.setEnabled(newJavaScriptEnabledSearchString.equals("Custom URL")); + break; + + case "javascript_enabled_search_custom_url": + // Set the new custom search URL as the summary text for `javascript_enabled_search_custom_url`. The default is `""`. + javaScriptEnabledSearchCustomURLPreference.setSummary(sharedPreferences.getString("javascript_enabled_search_custom_url", "")); + break; + + case "homepage": + // Set the new homepage URL as the summary text for the Homepage preference. The default is `https://www.duckduckgo.com`. + homepagePreference.setSummary(sharedPreferences.getString("homepage", "https://www.duckduckgo.com")); + break; + + case "default_font_size": + // Get the default font size as a string. The default is `100`. + String newDefaultFontSizeString = sharedPreferences.getString("default_font_size", "100"); + + // Update the summary text of `default_font_size`. + defaultFontSizePreference.setSummary(newDefaultFontSizeString + "%%"); + + default: + // If no match, do nothing. + break; + } + } + }; + + // Register the listener. + savedPreferences.registerOnSharedPreferenceChangeListener(preferencesListener); + } + + // It is necessary to re-register the listener on every resume or it will randomly stop working because apps can be paused and resumed at any time + // even while running in the foreground. + @Override + public void onPause() { + super.onPause(); + savedPreferences.unregisterOnSharedPreferenceChangeListener(preferencesListener); + } + + @Override + public void onResume() { + super.onResume(); + savedPreferences.registerOnSharedPreferenceChangeListener(preferencesListener); + } +} diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java new file mode 100644 index 00000000..051da668 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/BookmarksDatabaseHelper.java @@ -0,0 +1,448 @@ +/** + * 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.helpers; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +public class BookmarksDatabaseHelper extends SQLiteOpenHelper { + private static final int SCHEMA_VERSION = 1; + private static final String BOOKMARKS_DATABASE = "bookmarks.db"; + private static final String BOOKMARKS_TABLE = "bookmarks"; + + public static final String _ID = "_id"; + public static final String DISPLAY_ORDER = "displayorder"; + public static final String BOOKMARK_NAME = "bookmarkname"; + public static final String BOOKMARK_URL = "bookmarkurl"; + public static final String PARENT_FOLDER = "parentfolder"; + public static final String IS_FOLDER = "isfolder"; + public static final String FAVORITE_ICON = "favoriteicon"; + + // Initialize the database. The lint warnings for the unused parameters are suppressed. + public BookmarksDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory factory, @SuppressWarnings("UnusedParameters") int version) { + super(context, BOOKMARKS_DATABASE, factory, SCHEMA_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase bookmarksDatabase) { + // Create the database if it doesn't exist. + String CREATE_BOOKMARKS_TABLE = "CREATE TABLE " + BOOKMARKS_TABLE + " (" + + _ID + " integer primary key, " + + DISPLAY_ORDER + " integer, " + + BOOKMARK_NAME + " text, " + + BOOKMARK_URL + " text, " + + PARENT_FOLDER + " text, " + + IS_FOLDER + " boolean, " + + FAVORITE_ICON + " blob);"; + + bookmarksDatabase.execSQL(CREATE_BOOKMARKS_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase bookmarksDatabase, int oldVersion, int newVersion) { + // Code for upgrading the database will be added here when the schema version > 1. + } + + public void createBookmark(String bookmarkName, String bookmarkURL, int displayOrder, String parentFolder, byte[] favoriteIcon) { + ContentValues bookmarkContentValues = new ContentValues(); + + // ID is created automatically. + bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); + bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName); + bookmarkContentValues.put(BOOKMARK_URL, bookmarkURL); + bookmarkContentValues.put(PARENT_FOLDER, parentFolder); + bookmarkContentValues.put(IS_FOLDER, false); + bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon); + + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // The second argument is `null`, which makes it so that completely null rows cannot be created. Not a problem in our case. + bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public void createFolder(String folderName, int displayOrder, String parentFolder, byte[] favoriteIcon) { + ContentValues bookmarkContentValues = new ContentValues(); + + // ID is created automatically. + bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); + bookmarkContentValues.put(BOOKMARK_NAME, folderName); + bookmarkContentValues.put(PARENT_FOLDER, parentFolder); + bookmarkContentValues.put(IS_FOLDER, true); + bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon); + + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // The second argument is `null`, which makes it so that completely null rows cannot be created. Not a problem in our case. + bookmarksDatabase.insert(BOOKMARKS_TABLE, null, bookmarkContentValues); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public Cursor getBookmarkCursor(int databaseId) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // Prepare the SQL statement to get the `Cursor` for `databaseId` + final String GET_ONE_BOOKMARK = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + _ID + " = " + databaseId; + + // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. + // We can't close the `Cursor` because we need to use it in the parent activity. + return bookmarksDatabase.rawQuery(GET_ONE_BOOKMARK, null); + } + + public String getFolderName (int databaseId) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // Prepare the SQL statement to get the `Cursor` for the folder. + final String GET_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + _ID + " = " + databaseId; + + // Get `folderCursor`. The second argument is `null` because there are no `selectionArgs`. + Cursor folderCursor = bookmarksDatabase.rawQuery(GET_FOLDER, null); + + // Get `folderName`. + folderCursor.moveToFirst(); + String folderName = folderCursor.getString(folderCursor.getColumnIndex(BOOKMARK_NAME)); + + // Close the cursor and the database handle. + folderCursor.close(); + bookmarksDatabase.close(); + + // Return the folder name. + return folderName; + } + + public Cursor getFolderCursor(String folderName) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // SQL escape `folderName`. + folderName = DatabaseUtils.sqlEscapeString(folderName); + + // Prepare the SQL statement to get the `Cursor` for the folder. + final String GET_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + BOOKMARK_NAME + " = " + folderName + + " AND " + IS_FOLDER + " = " + 1; + + // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. + // We can't close the `Cursor` because we need to use it in the parent activity. + return bookmarksDatabase.rawQuery(GET_FOLDER, null); + } + + public Cursor getFoldersCursorExcept(String exceptFolders) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // Prepare the SQL statement to get the `Cursor` for the folders. + final String GET_FOLDERS_EXCEPT = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + IS_FOLDER + " = " + 1 + + " AND " + BOOKMARK_NAME + " NOT IN (" + exceptFolders + + ") ORDER BY " + BOOKMARK_NAME + " ASC"; + + // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. + // We can't close the `Cursor` because we need to use it in the parent activity. + return bookmarksDatabase.rawQuery(GET_FOLDERS_EXCEPT, null); + } + + public Cursor getSubfoldersCursor(String currentFolder) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // SQL escape `currentFolder. + currentFolder = DatabaseUtils.sqlEscapeString(currentFolder); + + // Prepare the SQL statement to get the `Cursor` for the subfolders. + final String GET_SUBFOLDERS = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + PARENT_FOLDER + " = " + currentFolder + + " AND " + IS_FOLDER + " = " + 1; + + // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. + // We can't close the `Cursor` because we need to use it in the parent activity. + return bookmarksDatabase.rawQuery(GET_SUBFOLDERS, null); + } + + public String getParentFolder(String currentFolder) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // SQL escape `currentFolder`. + currentFolder = DatabaseUtils.sqlEscapeString(currentFolder); + + // Prepare the SQL statement to get the parent folder. + final String GET_PARENT_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + IS_FOLDER + " = " + 1 + + " AND " + BOOKMARK_NAME + " = " + currentFolder; + + // The second argument is `null` because there are no `selectionArgs`. + Cursor bookmarkCursor = bookmarksDatabase.rawQuery(GET_PARENT_FOLDER, null); + bookmarkCursor.moveToFirst(); + + // Store the name of the parent folder. + String parentFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(PARENT_FOLDER)); + + // Close the `Cursor`. + bookmarkCursor.close(); + + return parentFolder; + } + + public Cursor getAllBookmarksCursor() { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // Get everything in the BOOKMARKS_TABLE. + final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE; + + // Return the results as a Cursor. The second argument is `null` because there are no selectionArgs. + // We can't close the Cursor because we need to use it in the parent activity. + return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null); + } + + public Cursor getAllBookmarksCursorByDisplayOrder(String folderName) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // SQL escape `folderName`. + folderName = DatabaseUtils.sqlEscapeString(folderName); + + // Get everything in the BOOKMARKS_TABLE. + final String GET_ALL_BOOKMARKS = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + PARENT_FOLDER + " = " + folderName + + " ORDER BY " + DISPLAY_ORDER + " ASC"; + + // Return the results as a Cursor. The second argument is `null` because there are no selectionArgs. + // We can't close the Cursor because we need to use it in the parent activity. + return bookmarksDatabase.rawQuery(GET_ALL_BOOKMARKS, null); + } + + public Cursor getBookmarksCursorExcept(long[] exceptIdLongArray, String folderName) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // Prepare a string that contains the comma-separated list of IDs not to get. + String doNotGetIdsString = ""; + // Extract the array to `doNotGetIdsString`. + for (long databaseIdLong : exceptIdLongArray) { + // If this is the first number, only add the number. + if (doNotGetIdsString.isEmpty()) { + doNotGetIdsString = String.valueOf(databaseIdLong); + } else { // If there already is a number in the string, place a `,` before the number. + doNotGetIdsString = doNotGetIdsString + "," + databaseIdLong; + } + } + + // SQL escape `folderName`. + folderName = DatabaseUtils.sqlEscapeString(folderName); + + // Prepare the SQL statement to select all items except those with the specified IDs. + final String GET_All_BOOKMARKS_EXCEPT_SPECIFIED = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + PARENT_FOLDER + " = " + folderName + + " AND " + _ID + " NOT IN (" + doNotGetIdsString + + ") ORDER BY " + DISPLAY_ORDER + " ASC"; + + // Return the results as a `Cursor`. The second argument is `null` because there are no `selectionArgs`. + // We can't close the `Cursor` because we need to use it in the parent activity. + return bookmarksDatabase.rawQuery(GET_All_BOOKMARKS_EXCEPT_SPECIFIED, null); + } + + public boolean isFolder(int databaseId) { + // Get a readable database handle. + SQLiteDatabase bookmarksDatabase = this.getReadableDatabase(); + + // Prepare the SQL statement to determine if `databaseId` is a folder. + final String CHECK_IF_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + _ID + " = " + databaseId; + + // Populate folderCursor. The second argument is `null` because there are no `selectionArgs`. + Cursor folderCursor = bookmarksDatabase.rawQuery(CHECK_IF_FOLDER, null); + + // Ascertain if this database ID is a folder. + folderCursor.moveToFirst(); + boolean isFolder = (folderCursor.getInt(folderCursor.getColumnIndex(IS_FOLDER)) == 1); + + // Close the `Cursor` and the database handle. + folderCursor.close(); + bookmarksDatabase.close(); + + return isFolder; + } + + public void updateBookmark(int databaseId, String bookmarkName, String bookmarkUrl) { + // Store the updated values in `bookmarkContentValues`. + ContentValues bookmarkContentValues = new ContentValues(); + + bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName); + bookmarkContentValues.put(BOOKMARK_URL, bookmarkUrl); + + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // Update the bookmark. The last argument is `null` because there are no `whereArgs`. + bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public void updateBookmark(int databaseId, String bookmarkName, String bookmarkUrl, byte[] favoriteIcon) { + // Store the updated values in `bookmarkContentValues`. + ContentValues bookmarkContentValues = new ContentValues(); + + bookmarkContentValues.put(BOOKMARK_NAME, bookmarkName); + bookmarkContentValues.put(BOOKMARK_URL, bookmarkUrl); + bookmarkContentValues.put(FAVORITE_ICON, favoriteIcon); + + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // Update the bookmark. The last argument is `null` because there are no `whereArgs`. + bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public void updateFolder(int databaseId, String oldFolderName, String newFolderName) { + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // Update the folder first. Store the updated values in `folderContentValues`. + ContentValues folderContentValues = new ContentValues(); + + folderContentValues.put(BOOKMARK_NAME, newFolderName); + + // Run the update on the folder. The last argument is `null` because there are no `whereArgs`. + bookmarksDatabase.update(BOOKMARKS_TABLE, folderContentValues, _ID + " = " + databaseId, null); + + + // Update the bookmarks inside the folder with the new parent folder name. + ContentValues bookmarkContentValues = new ContentValues(); + + bookmarkContentValues.put(PARENT_FOLDER, newFolderName); + + // SQL escape `oldFolderName`. + oldFolderName = DatabaseUtils.sqlEscapeString(oldFolderName); + + // Run the update on the bookmarks. The last argument is `null` because there are no `whereArgs`. + bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, PARENT_FOLDER + " = " + oldFolderName, null); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public void updateFolder(int databaseId, String oldFolderName, String newFolderName, byte[] folderIcon) { + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // Update the folder first. Store the updated values in `folderContentValues`. + ContentValues folderContentValues = new ContentValues(); + + folderContentValues.put(BOOKMARK_NAME, newFolderName); + folderContentValues.put(FAVORITE_ICON, folderIcon); + + // Run the update on the folder. The last argument is `null` because there are no `whereArgs`. + bookmarksDatabase.update(BOOKMARKS_TABLE, folderContentValues, _ID + " = " + databaseId, null); + + + // Update the bookmarks inside the folder with the new parent folder name. + ContentValues bookmarkContentValues = new ContentValues(); + + bookmarkContentValues.put(PARENT_FOLDER, newFolderName); + + // SQL escape `oldFolderName`. + oldFolderName = DatabaseUtils.sqlEscapeString(oldFolderName); + + // Run the update on the bookmarks. The last argument is `null` because there are no `whereArgs`. + bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, PARENT_FOLDER + " = " + oldFolderName, null); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public void updateBookmarkDisplayOrder(int databaseId, int displayOrder) { + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // Store the new display order in `bookmarkContentValues`. + ContentValues bookmarkContentValues = new ContentValues(); + bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); + + // Update the database. The last argument is `null` because there are no `whereArgs`. + bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public void moveToFolder(int databaseId, String newFolder) { + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // Get the highest `DISPLAY_ORDER` in the new folder + String newFolderSqlEscaped = DatabaseUtils.sqlEscapeString(newFolder); + final String NEW_FOLDER = "Select * FROM " + BOOKMARKS_TABLE + + " WHERE " + PARENT_FOLDER + " = " + newFolderSqlEscaped + + " ORDER BY " + DISPLAY_ORDER + " ASC"; + // The second argument is `null` because there are no `selectionArgs`. + Cursor newFolderCursor = bookmarksDatabase.rawQuery(NEW_FOLDER, null); + int displayOrder; + if (newFolderCursor.getCount() > 0) { + newFolderCursor.moveToLast(); + displayOrder = newFolderCursor.getInt(newFolderCursor.getColumnIndex(DISPLAY_ORDER)) + 1; + } else { + displayOrder = 0; + } + newFolderCursor.close(); + + // Store the new values in `bookmarkContentValues`. + ContentValues bookmarkContentValues = new ContentValues(); + bookmarkContentValues.put(DISPLAY_ORDER, displayOrder); + bookmarkContentValues.put(PARENT_FOLDER, newFolder); + + // Update the database. The last argument is 'null' because there are no 'whereArgs'. + bookmarksDatabase.update(BOOKMARKS_TABLE, bookmarkContentValues, _ID + " = " + databaseId, null); + + // Close the database handle. + bookmarksDatabase.close(); + } + + public void deleteBookmark(int databaseId) { + // Get a writable database handle. + SQLiteDatabase bookmarksDatabase = this.getWritableDatabase(); + + // Deletes the row with the given databaseId. The last argument is null because we don't need additional parameters. + bookmarksDatabase.delete(BOOKMARKS_TABLE, _ID + " = " + databaseId, null); + + // Close the database handle. + bookmarksDatabase.close(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/OrbotProxyHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/OrbotProxyHelper.java new file mode 100644 index 00000000..97b2f9d7 --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/OrbotProxyHelper.java @@ -0,0 +1,104 @@ +/** + * 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.helpers; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Proxy; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; +import android.util.ArrayMap; +import android.util.Log; + +import com.stoutner.privacybrowser.activities.MainWebView; +import com.stoutner.privacybrowser.R; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class OrbotProxyHelper { + public static void setProxy(Context privacyBrowserContext, Activity parentActivity, String proxyHost, String proxyPort) { + // Set the proxy values + System.setProperty("http.proxyHost", proxyHost); + System.setProperty("http.proxyPort", proxyPort); + System.setProperty("https.proxyHost", proxyHost); + System.setProperty("https.proxyPort", proxyPort); + + // Use reflection to apply the new proxy values. + try { + Class applicationClass = Class.forName("android.app.Application"); + Field mLoadedApkField = applicationClass.getDeclaredField("mLoadedApk"); + // `setAccessible(true)` allows us to change the value of `mLoadedApkField`. + mLoadedApkField.setAccessible(true); + Object mLoadedApkObject = mLoadedApkField.get(privacyBrowserContext); + + Class loadedApkClass = Class.forName("android.app.LoadedApk"); + Field mReceiversField = loadedApkClass.getDeclaredField("mReceivers"); + // `setAccessible(true)` allows us to change the value of `mReceiversField`. + mReceiversField.setAccessible(true); + + ArrayMap receivers = (ArrayMap) mReceiversField.get(mLoadedApkObject); + + for (Object receiverMap : receivers.values()) { + for (Object receiver : ((ArrayMap) receiverMap).keySet()) { + // We have to use `Class`, which is an `unbounded wildcard parameterized type`, instead of `Class`, which is a `raw type`, or `receiveClass.getDeclaredMethod` below will produce an error. + Class receiverClass = receiver.getClass(); + if (receiverClass.getName().contains("ProxyChangeListener")) { + Method onReceiveMethod = receiverClass.getDeclaredMethod("onReceive", Context.class, Intent.class); + Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); + onReceiveMethod.invoke(receiver, privacyBrowserContext, intent); + } + } + } + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) { + Log.d("enableProxyThroughOrbot", "Exception: " + exception); + } + + 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)); + + try { // Check to see if Orbot is installed. + PackageManager packageManager = privacyBrowserContext.getPackageManager(); + packageManager.getPackageInfo("org.torproject.android", PackageManager.GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException exception){ // If an exception is thrown, Orbot is not installed. + // Build an `AlertDialog`. `R.style.LightAlertDialog` formats the color of the button text. + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(parentActivity, R.style.LightAlertDialog); + dialogBuilder.setMessage(R.string.orbot_proxy_not_installed); + dialogBuilder.setPositiveButton(R.string.close, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing. The `AlertDialog` will close automatically. + } + }); + + // Convert `dialogBuilder` to `alertDialog` and display it on the screen. + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + } + } else { // Otherwise set the default grey `appBar` background. + MainWebView.appBar.setBackgroundDrawable(ContextCompat.getDrawable(privacyBrowserContext, R.color.grey_100)); + } + } +} diff --git a/app/src/main/res/layout/bare_webview.xml b/app/src/main/res/layout/bare_webview.xml index 7960ca5e..9e628092 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 . --> - + . --> - + diff --git a/app/src/main/res/menu/webview_options_menu.xml b/app/src/main/res/menu/webview_options_menu.xml index 8dee5b1d..06fa1ac4 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=".MainWebViewActivity"> + tools:context=".activities.MainWebView"> Privacy Browser Einstellungen de - + Privatsphäre-Modus JavaScript aktiviert JavaScript deaktiviert diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8d6f5c7..9af909e2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,7 +28,7 @@ Privacy Browser Settings en - + Privacy Mode JavaScript enabled JavaScript disabled diff --git a/app/src/standard/java/com/stoutner/privacybrowser/BannerAd.java b/app/src/standard/java/com/stoutner/privacybrowser/BannerAd.java index a6f98a6d..caf6e623 100644 --- a/app/src/standard/java/com/stoutner/privacybrowser/BannerAd.java +++ b/app/src/standard/java/com/stoutner/privacybrowser/BannerAd.java @@ -22,28 +22,28 @@ package com.stoutner.privacybrowser; import android.content.Context; import android.view.View; -class BannerAd { - static void requestAd(View view) { +public class BannerAd { + public static void requestAd(View view) { // Do nothing because this is the standard flavor. } - static void reloadAfterRotate(View view, Context applicationContext, String ad_id) { + public static void reloadAfterRotate(View view, Context applicationContext, String ad_id) { // Do nothing because this is the standard flavor. } - static void hideAd(View view) { + public static void hideAd(View view) { // Do nothing because this is the standard flavor. } - static void showAd(View view) { + public static void showAd(View view) { // Do nothing because this is the standard flavor. } - static void pauseAd(View view) { + public static void pauseAd(View view) { // Do nothing because this is the standard flavor. } - static void resumeAd(View view) { + public static void resumeAd(View view) { // Do nothing because this is the standard flavor. } } \ No newline at end of file