/*
- * Copyright © 2015-2017 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
*
* Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
*
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.NavigationView;
import android.support.design.widget.Snackbar;
-import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
+// `ShortcutInfoCompat`, `ShortcutManagerCompat`, and `IconCompat` can be switched to the non-compat version once API >= 26.
+import android.support.v4.content.pm.ShortcutInfoCompat;
+import android.support.v4.content.pm.ShortcutManagerCompat;
+import android.support.v4.graphics.drawable.IconCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.text.Spanned;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
+import android.util.Log;
import android.util.Patterns;
import android.view.ContextMenu;
import android.view.GestureDetector;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.webkit.CookieManager;
-import android.webkit.DownloadListener;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
-import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.WebViewDatabase;
-import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import com.stoutner.privacybrowser.BannerAd;
import com.stoutner.privacybrowser.BuildConfig;
import com.stoutner.privacybrowser.R;
+import com.stoutner.privacybrowser.dialogs.AddDomainDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Pattern;
// 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 CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener,
- DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener,
- NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
-
- // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`,
- // `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `HttpAuthenticationDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`,
- // `ViewSslCertificateDialog`, `CreateHomeScreenShortcutDialog`, and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
+public class MainWebViewActivity extends AppCompatActivity implements AddDomainDialog.AddDomainListener, CreateBookmarkDialog.CreateBookmarkListener,
+ CreateBookmarkFolderDialog.CreateBookmarkFolderListener, CreateHomeScreenShortcutDialog.CreateHomeScreenSchortcutListener, DownloadFileDialog.DownloadFileListener,
+ DownloadImageDialog.DownloadImageListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener,
+ NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener, SslCertificateErrorDialog.SslCertificateErrorListener,
+ UrlHistoryDialog.UrlHistoryListener {
+
+ // `darkTheme` is public static so it can be accessed from `AboutActivity`, `GuideActivity`, `AddDomainDialog`, `SettingsActivity`, `DomainsActivity`, `DomainsListFragment`, `BookmarksActivity`,
+ // `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `DownloadFileDialog`, `DownloadImageDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
+ // `EditBookmarkDatabaseViewDialog`, `HttpAuthenticationDialog`, `MoveToFolderDialog`, `SslCertificateErrorDialog`, `UrlHistoryDialog`, `ViewSslCertificateDialog`, `CreateHomeScreenShortcutDialog`,
+ // and `OrbotProxyHelper`. It is also used in `onCreate()`, `applyAppSettings()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
public static boolean darkTheme;
- // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `CreateBookmarkDialog`, `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`,
- // and `ViewSslCertificateDialog`. It is also used in `onCreate()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
+ // `favoriteIconBitmap` is public static so it can be accessed from `CreateHomeScreenShortcutDialog`, `BookmarksActivity`, `BookmarksDatabaseViewActivity`, `CreateBookmarkDialog`,
+ // `CreateBookmarkFolderDialog`, `EditBookmarkDialog`, `EditBookmarkFolderDialog`, `EditBookmarkDatabaseViewDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`,
+ // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcutCreate()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
public static Bitmap favoriteIconBitmap;
// `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
// It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
public static String formattedUrlString;
- // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`, and `ViewSslCertificateDialog`. It is also used in `onCreate()`.
+ // `sslCertificate` is public static so it can be accessed from `DomainsActivity`, `DomainsListFragment`, `DomainSettingsFragment`, `PinnedSslCertificateMismatchDialog`,
+ // and `ViewSslCertificateDialog`. It is also used in `onCreate()`.
public static SslCertificate sslCertificate;
// `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`.
// `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
public static String webViewTitle;
+ // `appliedUserAgentString` is public static so it can be accessed from `ViewSourceActivity`. It is also used in `applyDomainSettings()`.
+ public static String appliedUserAgentString;
+
// `displayWebpageImagesBoolean` is public static so it can be accessed from `DomainSettingsFragment`. It is also used in `applyAppSettings()` and `applyDomainSettings()`.
public static boolean displayWebpageImagesBoolean;
// `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
public static boolean restartFromBookmarksActivity;
- // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and
- // `loadBookmarksFolder()`.
+ // `easyListVersion` is public static so it can be accessed from `AboutTabFragment`. It is also used in `onCreate()`.
+ public static String easyListVersion;
+
+ // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
+ // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
public static String currentBookmarksFolder;
- // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
+ // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
public static int domainSettingsDatabaseId;
+
+ // The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
public static String pinnedDomainSslIssuedToCNameString;
public static String pinnedDomainSslIssuedToONameString;
public static String pinnedDomainSslIssuedToUNameString;
// `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
private CoordinatorLayout rootCoordinatorLayout;
- // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`
- // `onSslMismatchBack()`, and `setDisplayWebpageImages()`.
+ // `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
+ // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `setDisplayWebpageImages()`.
private WebView mainWebView;
// `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
// `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
private boolean translucentNavigationBarOnFullscreen;
- // `currentDomainName` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
+ // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
+ private boolean reapplyDomainSettingsOnRestart;
+
+ // `currentDomainName` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onAddDomain()`, and `applyDomainSettings()`.
private String currentDomainName;
// `ignorePinnedSslCertificateForDomain` is used in `onCreate()`, `onSslMismatchProceed()`, and `applyDomainSettings()`.
// `waitingForOrbot` is used in `onCreate()` and `applyAppSettings()`.
private boolean waitingForOrbot;
- // `domainSettingsApplied` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
+ // `domainSettingsApplied` is used in `prepareOptionsMenu()`, `applyDomainSettings()`, and `setDisplayWebpageImages()`.
private boolean domainSettingsApplied;
// `displayWebpageImagesInt` is used in `applyDomainSettings()` and `setDisplayWebpageImages()`.
// `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
private EditText urlTextBox;
- // `redColorSpan` is used in `onCreate()` and `highlightUrlText()`.
+ // The color spans are used in `onCreate()` and `highlightUrlText()`.
private ForegroundColorSpan redColorSpan;
-
- // `initialGrayColorSpan` is sued in `onCreate()` and `highlightUrlText()`.
private ForegroundColorSpan initialGrayColorSpan;
-
- // `finalGrayColorSpam` is used in `onCreate()` and `highlightUrlText()`.
private ForegroundColorSpan finalGrayColorSpan;
// `adView` is used in `onCreate()` and `onConfigurationChanged()`.
@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")
+ @SuppressLint({"SetJavaScriptEnabled"})
// Remove Android Studio's warning about deprecations. We have to use the deprecated `getColor()` until API >= 23.
@SuppressWarnings("deprecation")
protected void onCreate(Bundle savedInstanceState) {
// Run the default commands.
super.onCreate(savedInstanceState);
+ Log.i("BlockLists", "Begin populating block lists.");
+
+ // Initialize the block lists.
+ List<String> mainWhiteList = new LinkedList<>();
+ List<String[]> multiEntryWhiteList = new LinkedList<>();
+ List<String[]> domainWhiteList = new LinkedList<>();
+ List<String[]> thirdPartyDomainWhiteList = new LinkedList<>();
+ List<String> mainBlockList = new LinkedList<>();
+ List<String> initialBlockList = new LinkedList<>();
+ List<String> finalBlockList = new LinkedList<>();
+ List<String> thirdPartyBlockList = new LinkedList<>();
+ List<String> thirdPartyInitialBlockList = new LinkedList<>();
+ List<String[]> multiEntryBlockList = new LinkedList<>();
+ List<String[]> multiEntryInitialBlockList = new LinkedList<>();
+ List<String[]> multiEntryFinalBlockList = new LinkedList<>();
+ List<String[]> domainBlockList = new LinkedList<>();
+ List<String[]> domainInitialBlockList = new LinkedList<>();
+ List<String[]> domainFinalBlockList = new LinkedList<>();
+ List<String[]> domainRegularExpressionBlockList = new LinkedList<>();
+ List<String[]> thirdPartyMultiEntryBlockList = new LinkedList<>();
+ List<String[]> thirdPartyMultiEntryInitialBlockList = new LinkedList<>();
+ List<String[]> thirdPartyDomainBlockList = new LinkedList<>();
+ List<String> regularExpressionBlockList = new LinkedList<>();
+ List<String> thirdPartyRegularExpressionBlockList = new LinkedList<>();
+
+ // Populate the block lists.
+ try {
+ // Load `easylist.txt` into a `BufferedReader`.
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("easylist.txt")));
+
+ // Create a string for storing the block list entries.
+ String blockListEntry;
+
+ // Parse EasyList.
+ while ((blockListEntry = bufferedReader.readLine()) != null) {
+ // Remove any `^` from the block list entry. Privacy Browser does not process them in the interest of efficiency.
+ blockListEntry = blockListEntry.replace("^", "");
+
+ //noinspection StatementWithEmptyBody
+ if (blockListEntry.contains("##") || blockListEntry.contains("#?#") || blockListEntry.contains("#@#") || blockListEntry.startsWith("[")) {
+ // Entries that contain `##`, `#?#`, and `#@#` are for hiding elements in the main page's HTML. Entries that start with `[` describe the AdBlock compatibility level.
+
+ // Do nothing. Privacy Browser does not currently use these entries.
+
+ //Log.i("BlackLists", "Not added: " + adBlockerEntry);
+ } else if (blockListEntry.startsWith("!")){ // Entries that begin with `!` are comments.
+ if (blockListEntry.startsWith("! Version:")) {
+ // Store the EasyList version number.
+ easyListVersion = blockListEntry.substring(11);
+ }
+
+ //Log.i("BlackLists", "Not added: " + adBlockerEntry);
+ } else if (blockListEntry.startsWith("@@")) { // Entries that begin with `@@` are whitelists.
+ // Remove the `@@`
+ blockListEntry = blockListEntry.substring(2);
+
+ // Strip out an initial `||` Privacy Browser doesn't differentiate against items that only match against the end of the domain name.
+ if (blockListEntry.startsWith("||")) {
+ blockListEntry = blockListEntry.substring(2);
+ }
+
+ // TODO
+
+ // mainWhiteList.add(blockListEntry);
+
+ // Log.i("BlockLists", "Main white list added: " + blockListEntry);
+ } else if (blockListEntry.endsWith("|")){ // Entries that end with `|` match against the end of the URL.
+ // Strip out the final "|"
+ blockListEntry = blockListEntry.substring(0, blockListEntry.length() - 1);
+
+ // Strip out any initial `||`. They are redundant in this case because the block list entry is being matched against the end of the URL.
+ if (blockListEntry.startsWith("||")) {
+ blockListEntry = blockListEntry.substring(2);
+ }
+
+ if (blockListEntry.contains("*")) { // Process a double final entry.
+ int wildcardIndex = blockListEntry.indexOf("*");
+
+ String firstEntry = blockListEntry.substring(0, wildcardIndex);
+ String secondEntry = blockListEntry.substring(wildcardIndex + 1);
+
+
+ String[] doubleEntry = {firstEntry, secondEntry};
+
+ multiEntryFinalBlockList.add(doubleEntry);
+
+ //Log.i("BlockLists", "Multi entry final block list added: " + firstEntry + " , " + secondEntry).
+ } else { // Process a standard final entry.
+ finalBlockList.add(blockListEntry);
+
+ //Log.i("BlockLists", "Final block list added: " + blockListEntry);
+ }
+ } else if (blockListEntry.contains("$")) { // Entries that contain `$` use filter options.
+ // Strip out any initial `||`. These will be treated like any other entry.
+ if (blockListEntry.startsWith("||")) {
+ blockListEntry = blockListEntry.substring(2);
+ }
+
+ if (blockListEntry.contains("third-party")) { // Process third party blocklist entries.
+ //noinspection StatementWithEmptyBody
+ if (blockListEntry.contains("~third-party")) { // Process third-party white list entries.
+ // Do not process these white list entries. They are designed to combine with block filters that Privacy Browser doesn't use, like `subdocument` and `xmlhttprequest`.
+
+ // Log.i("BlockLists", "Not added: " + blockListEntry);
+ } else if (blockListEntry.contains("domain=")) { // Process third-party domain block list entries.
+ if (blockListEntry.startsWith("|")) { // Third-party domain initial block list entries.
+
+ } else if (blockListEntry.contains("\\")) { // Third-party domain regular expressions.
+
+ } else { // Third-party domain entries.
+ // Parse the entry
+ String entry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+ String filters = blockListEntry.substring(blockListEntry.indexOf("$") + 1);
+ String domains = filters.substring(filters.indexOf("domain=") + 7);
+
+ // Strip any trailing "*" on the entries.
+ if (entry.endsWith("*")) {
+ entry = entry.substring(0, entry.length() - 1);
+ }
+
+ if (entry.contains("*")) { // Third-party domain multi-entry.
+
+ } else { // Third-party domain single entry.
+
+ boolean whiteListDomain = false;
+
+ // Process each domain.
+ do {
+ String domain;
+
+ if (domains.contains("|")) {
+ // Get the first domain
+ domain = domains.substring(0, domains.indexOf("|"));
+
+ // Remove the first domain from the list.
+ domains = domains.substring(domains.indexOf("|") + 1);
+ } else {
+ domain = domains;
+ }
+
+ // Differentiate between block list domains and white list domains.
+ if (domain.startsWith("~")) { // White list third-party domain entry.
+ // Strip the initial `~`.
+ domain = domain.substring(1);
+
+ // Set the white list domain flag and store the domain.
+ whiteListDomain = true;
+
+ // Create the domain entry.
+ String[] domainEntry = {domain, entry};
+
+ // Add the entry to the third-party domain white list.
+ thirdPartyDomainWhiteList.add(domainEntry);
+
+ Log.i("BlockLists", "Third-party domain white list added: " + domain + " , " + entry);
+ } else { // Block list third-party domain entry.
+ String[] domainEntry = {domain, entry};
+
+ thirdPartyDomainBlockList.add(domainEntry);
+
+ // Log.i("BlockLists", "Third-party domain block list added: " + domain + " , " + entry);
+ }
+ } while (domains.contains("|"));
+
+ // Add a third-party block list entry if a white list domain was processed.
+ if (whiteListDomain) {
+ // Add an entry to the third-party block list.
+ thirdPartyBlockList.add(entry);
+
+ Log.i("BlockLists", "Third-party block list added: " + entry);
+ }
+ }
+ }
+ } else if (blockListEntry.startsWith("|")) { // Third-party initial block list entries.
+ // Strip the initial `|`.
+ blockListEntry = blockListEntry.substring(1);
+
+ // Get the entry.
+ String entry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+
+ if (entry.contains("*")) { // Process a third-party multi-entry initial block list.
+ int wildcardIndex = entry.indexOf("*");
+
+ String firstEntry = entry.substring(0, wildcardIndex);
+ String secondEntry = entry.substring(wildcardIndex + 1);
+
+ String[] thirdPartyDoubleEntry = {firstEntry, secondEntry};
+
+ thirdPartyMultiEntryInitialBlockList.add(thirdPartyDoubleEntry);
+
+ //Log.i("BlockLists", "Third-party multi-entry initial block list added: " + firstEntry + " , " + secondEntry);
+ } else {
+ thirdPartyInitialBlockList.add(entry);
+
+ //Log.i("BlockLists", "Third-party initial block list added: " + entry);
+ }
+ } else if (blockListEntry.contains("*")) { // Process third-party multi-entry or regular expression blocklist entries.
+ // Get the entry.
+ String entry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+
+ if (entry.endsWith("*")) {
+ // Strip the final `*`.
+ entry = entry.substring(0, entry.length() - 1);
+
+ // Add the entry to the block list.
+ thirdPartyBlockList.add(entry);
+
+ //Log.i("BlockLists", "Third party block list added: " + entry);
+ } else if (entry.contains("\\")) { // Process a third-party regular expression.
+ // Add the entry to the third-party regular expression block list.
+ thirdPartyRegularExpressionBlockList.add(entry);
+
+ //Log.i("BlockLists", "Third-party regular expression block list added: " + entry);
+ } else { // There are two or more entries.
+ int wildcardIndex = entry.indexOf("*");
+
+ String firstEntry = entry.substring(0, wildcardIndex);
+ String secondEntry = entry.substring(wildcardIndex + 1);
+
+ if (secondEntry.contains("*")) { // there are three or more entries.
+ int secondWildcardIndex = secondEntry.indexOf("*");
+
+ String realSecondEntry = secondEntry.substring(0, secondWildcardIndex);
+ String thirdEntry = secondEntry.substring(secondWildcardIndex + 1);
+
+ if (thirdEntry.contains("*")) { // Process a third-party quadruple entry.
+ int thirdWildcardIndex = thirdEntry.indexOf("*");
+
+ String realThirdEntry = thirdEntry.substring(0, thirdWildcardIndex);
+ String fourthEntry = thirdEntry.substring(thirdWildcardIndex + 1);
+
+ String[] thirdPartyQuadrupleEntry = {firstEntry, realSecondEntry, realThirdEntry, fourthEntry};
+
+ thirdPartyMultiEntryBlockList.add(thirdPartyQuadrupleEntry);
+
+ //Log.i("BlockLists", "Third-party multi-entry block list added: " + firstEntry + " , " + realSecondEntry + " , " + realThirdEntry + " , " + fourthEntry);
+ } else { // Process a third-party triple entry.
+ String[] thirdPartyTripleEntry = {firstEntry, realSecondEntry, thirdEntry};
+
+ thirdPartyMultiEntryBlockList.add(thirdPartyTripleEntry);
+
+ //Log.i("BlockLists", "Third-party multi-entry block list added: " + firstEntry + " , " + realSecondEntry + " , " + thirdEntry);
+ }
+ } else { // Process a third-party double entry.
+ String[] thirdPartyDoubleEntry = {firstEntry, secondEntry};
+
+ thirdPartyMultiEntryBlockList.add(thirdPartyDoubleEntry);
+
+ //Log.i("BlockLists", "Third-party multi-entry block list added: " + firstEntry + " , " + secondEntry);
+ }
+ }
+ } else { // Process standard third party entries.
+ // Get the entry.
+ String entry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+
+ // Add the entry to the block list.
+ thirdPartyBlockList.add(entry);
+
+ //Log.i("BlockLists", "Third party block list added: " + entry);
+ }
+ } else if (blockListEntry.substring(blockListEntry.indexOf("$")).contains("domain")) {
+ if (blockListEntry.contains("~")) { // Whitelist.
+ // Separate the filters.
+ String entry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+ String filters = blockListEntry.substring(blockListEntry.indexOf("$") + 1);
+ String domains = filters.substring(filters.indexOf("domain=") + 7);
+
+ // Strip any final `*` from the entry. They are redundant.
+ if (entry.endsWith("*")) {
+ entry = entry.substring(0, entry.length() - 1);
+ }
+
+ // Process each domain.
+ do {
+ String domain;
+
+ if (domains.contains("|")) {
+ // Get the first domain
+ domain = domains.substring(0, domains.indexOf("|"));
+
+ // Remove the first domain from the list.
+ domains = domains.substring(domains.indexOf("|") + 1);
+ } else {
+ domain = domains;
+ }
+
+ // Strip the initial `~`.
+ domain = domain.substring(1);
+
+ if (entry.contains("*")) { // Process a double entry.
+ int wildcardIndex = entry.indexOf("*");
+
+ String firstEntry = entry.substring(0, wildcardIndex);
+ String secondEntry = entry.substring(wildcardIndex + 1);
+
+ String[] domainDoubleEntry = {firstEntry, secondEntry};
+
+ domainWhiteList.add(domainDoubleEntry);
+
+ //Log.i("BlockLists", "Domain white list added: " + domain + " , " + firstEntry + " , " + secondEntry);
+ } else { // Process a single entry.
+ String[] domainEntry = {domain, entry};
+
+ domainWhiteList.add(domainEntry);
+
+ //Log.i("BlockLists", "Domain white list added: " + domain + " , " + entry);
+ }
+ } while (domains.contains("|"));
+ } else { // The block list entry contains a domain, but not a third party designation and isn't a whitelist.
+ // Separate the filters.
+ String entry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+ String filters = blockListEntry.substring(blockListEntry.indexOf("$") + 1);
+ String domains = filters.substring(filters.indexOf("domain=") + 7);
+
+ // Only process the block list item if the entry is not null. Some lines in EasyList begin with `$websocket`, which create a null entry.
+ if (!entry.equals("")) {
+ do {
+ String domain;
+
+ if (domains.contains("|")) {
+ // Get the first domain.
+ domain = domains.substring(0, domains.indexOf("|"));
+
+ // Remove the first domain from the list.
+ domains = domains.substring(domains.indexOf("|") + 1);
+ } else {
+ domain = domains;
+ }
+
+ if (entry.contains("*")) {
+ int wildcardIndex = entry.indexOf("*");
+
+ String firstEntry = entry.substring(0, wildcardIndex);
+ String secondEntry = entry.substring(wildcardIndex + 1);
+
+ String[] domainDoubleEntry = {domain, firstEntry, secondEntry};
+
+ domainBlockList.add(domainDoubleEntry);
+
+ //Log.i("BlockLists", "Domain double entry block list added: " + domain + " , " + firstEntry + " , " + secondEntry);
+ } else if (entry.startsWith("|")) {
+ // Remove the initial `|`;
+ String entryBase = entry.substring(1);
+
+ //noinspection StatementWithEmptyBody
+ if (entryBase.equals("http://") || entryBase.equals("https://")) {
+ // Do nothing. These entries will entirely block the website.
+ // Often the original entry blocks `$script` but Privacy Browser does not currently differentiate between scripts and other entries.
+ } else {
+ String[] domainEntry = {domain, entryBase};
+
+ domainInitialBlockList.add(domainEntry);
+
+ //Log.i("BlockLists", "Domain initial block list added: " + domain + " , " + entryBase);
+ }
+ } else if (entry.endsWith("|")) {
+ // Remove the final `|`.
+ String entryBase = entry.substring(0, entry.length() - 1);
+
+ String[] domainEntry = {domain, entryBase};
+
+ domainFinalBlockList.add(domainEntry);
+
+ //Log.i("BlockLists", "Domain final block list added: " + domain + " , " + entryBase);
+ } else if (entry.contains("\\")) {
+ String[] domainEntry = {domain, entry};
+
+ domainRegularExpressionBlockList.add(domainEntry);
+
+ // Log.i("BlockLists", "Domain regular expression block list added: " + domain + " , " + entry);
+ } else {
+ String[] domainEntry = {domain, entry};
+
+ domainBlockList.add(domainEntry);
+
+ //Log.i("BlockLists", "Domain block list added: " + domain + " , " + entry);
+ }
+ } while (domains.contains("|"));
+ }
+ }
+ } else if (blockListEntry.contains("~")) { // Whitelist entries.
+ // Remove the filter options.
+ blockListEntry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+
+ // Strip any trailing `*`.
+ if (blockListEntry.endsWith("*")) {
+ blockListEntry = blockListEntry.substring(0, blockListEntry.length() -1);
+ }
+
+ if (blockListEntry.contains("*")) {
+ int wildcardIndex = blockListEntry.indexOf("*");
+
+ String firstEntry = blockListEntry.substring(0, wildcardIndex);
+ String secondEntry = blockListEntry.substring(wildcardIndex + 1);
+
+
+ String[] doubleEntry = {firstEntry, secondEntry};
+
+ multiEntryWhiteList.add(doubleEntry);
+
+ //Log.i("BlockLists", "Multi entry white list added: " + firstEntry + " , " + secondEntry);
+ } else {
+ mainWhiteList.add(blockListEntry);
+
+ // Log.i("BlockLists", "Main white list added: " + blockListEntry);
+ }
+ } else if (blockListEntry.contains("\\")) { // Regular expressions.
+ // Remove the filter options.
+ blockListEntry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+
+ regularExpressionBlockList.add(blockListEntry);
+
+ //Log.i("BlockLists", "Regular expression list added: " + blockListEntry);
+ } else {
+ // Remove the filter options.
+ blockListEntry = blockListEntry.substring(0, blockListEntry.indexOf("$"));
+
+ // Strip any trailing `*`. These are redundant.
+ if (blockListEntry.endsWith("*")) {
+ blockListEntry = blockListEntry.substring(0, blockListEntry.length() - 1);
+ }
+
+ if (blockListEntry.contains("*")) { // Use a multi entry list.
+ int wildcardIndex = blockListEntry.indexOf("*");
+
+ String firstEntry = blockListEntry.substring(0, wildcardIndex);
+ String secondEntry = blockListEntry.substring(wildcardIndex + 1);
+
+ // Remove `.*` if it appears at the beginning of the second entry.
+ if (secondEntry.startsWith(".*")) {
+ secondEntry = secondEntry.substring(2);
+ }
+
+ // Create a third entry if required.
+ if (secondEntry.contains("*")) {
+ wildcardIndex = secondEntry.indexOf("*");
+
+ String thirdEntry = secondEntry.substring(wildcardIndex + 1);
+ secondEntry = secondEntry.substring(0, wildcardIndex);
+
+
+ if (thirdEntry.endsWith("|")) {
+ thirdEntry = thirdEntry.substring(0, thirdEntry.length() - 1);
+
+ String[] tripleEntry = {firstEntry, secondEntry, thirdEntry};
+
+ multiEntryFinalBlockList.add(tripleEntry);
+
+ //Log.i("BlockLists", "Multi entry final tripple block list added: " + firstEntry + " , " + secondEntry + " , " + thirdEntry);
+ } else {
+ String[] tripleEntry = {firstEntry, secondEntry, thirdEntry};
+
+ multiEntryBlockList.add(tripleEntry);
+
+ // Log.i("BlockLists", "Multi entry tripple block list added: " + firstEntry + " , " + secondEntry + " , " + thirdEntry);
+ }
+ } else { // This is a double entry.
+ if (firstEntry.startsWith("|")) {
+ String[] doubleEntry = {firstEntry.substring(1), secondEntry};
+
+ multiEntryInitialBlockList.add(doubleEntry);
+
+ //Log.i("BlockLists", "Multi entry initial block list added: " + firstEntry.substring(1) + " , " + secondEntry);
+ } else {
+ String[] doubleEntry = {firstEntry, secondEntry};
+
+ multiEntryBlockList.add(doubleEntry);
+
+ //Log.i("BlockLists", "Multi entry block list added: " + firstEntry + " , " + secondEntry);
+ }
+ }
+ } else if (blockListEntry.startsWith("|")) { // Populate the initial block list.
+ // Strip the initial `|`.
+ blockListEntry = blockListEntry.substring(1);
+
+ // Populate the initial block list.
+ initialBlockList.add(blockListEntry);
+
+ //Log.i("BlockLists", "Initial block list added: " + blockListEntry);
+ } else { // Populate the main block list.
+ mainBlockList.add(blockListEntry);
+
+ //Log.i("BlockLists", "Main block list added: " + blockListEntry);
+ }
+ }
+ } else { // Populate the standard lists.
+ // Strip out any initial `||`. These will be treated like any other entry.
+ if (blockListEntry.startsWith("||")) {
+ blockListEntry = blockListEntry.substring(2);
+ }
+
+ // Strip out any initial `*`.
+ if (blockListEntry.startsWith("*")) {
+ blockListEntry = blockListEntry.substring(1);
+ }
+
+ // Strip out any trailing `*`.
+ if (blockListEntry.endsWith("*")) {
+ blockListEntry = blockListEntry.substring(0, blockListEntry.length() - 1);
+ }
+
+ if (blockListEntry.contains("*")) { // Entries that contain a `*` in the middle have to be treated specially.
+ int wildcardIndex = blockListEntry.indexOf("*");
+
+ String firstEntry = blockListEntry.substring(0, wildcardIndex);
+ String secondEntry = blockListEntry.substring(wildcardIndex + 1);
+
+ // Remove `.*` if it appears at the beginning of the second entry.
+ if (secondEntry.startsWith(".*")) {
+ secondEntry = secondEntry.substring(2);
+ }
+
+ // Create a third entry if required.
+ if (secondEntry.contains("*")) {
+ wildcardIndex = secondEntry.indexOf("*");
+
+ String thirdEntry = secondEntry.substring(wildcardIndex + 1);
+ secondEntry = secondEntry.substring(0, wildcardIndex);
+
+ String[] tripleEntry = {firstEntry, secondEntry, thirdEntry};
+
+ multiEntryBlockList.add(tripleEntry);
+
+ //Log.i("BlockLists", "Multi entry tripple block list added: " + firstEntry + " , " + secondEntry + " , " + thirdEntry);
+ } else {
+ if (firstEntry.startsWith("|")) {
+ String[] doubleEntry = {firstEntry.substring(1), secondEntry};
+
+ multiEntryInitialBlockList.add(doubleEntry);
+
+ //Log.i("BlockLists", "Multi entry initial block list added: " + firstEntry.substring(1) + " , " + secondEntry);
+ } else {
+ String[] doubleEntry = {firstEntry, secondEntry};
+
+ multiEntryBlockList.add(doubleEntry);
+
+ //Log.i("BlockLists", "Multi entry block list added: " + firstEntry + " , " + secondEntry);
+ }
+ }
+ } else { // This is a basic entry.
+ // Add the modified block list entry to the main block list.
+ mainBlockList.add(blockListEntry);
+
+ //Log.i("BlockLists", "Main block list added: " + blockListEntry);
+ }
+ }
+ }
+
+ // Close `bufferedReader`.
+ bufferedReader.close();
+ } catch (IOException e) {
+ // The asset exists, so the `IOException` will never be thrown.
+ }
+
+ Log.i("BlockLists", "Finish populating block lists");
+
// Set the content view.
setContentView(R.layout.main_drawerlayout);
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
- supportAppBar = (Toolbar) findViewById(R.id.app_bar);
+ supportAppBar = findViewById(R.id.app_bar);
setSupportActionBar(supportAppBar);
appBar = getSupportActionBar();
appBar.setCustomView(R.layout.url_app_bar);
appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- // Initialize the `ForegroundColorSpans` and `StyleSpan` for highlighting `urlTextBox`. We have to use the deprecated `getColor()` until API >= 23.
+ // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
// Get a handle for `urlTextBox`.
- urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.url_edittext);
+ urlTextBox = findViewById(R.id.url_edittext);
// Remove the formatting from `urlTextBar` when the user is editing the text.
- urlTextBox.setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (hasFocus) { // The user is editing `urlTextBox`.
- // Remove the highlighting.
- urlTextBox.getText().removeSpan(redColorSpan);
- urlTextBox.getText().removeSpan(initialGrayColorSpan);
- urlTextBox.getText().removeSpan(finalGrayColorSpan);
- } else { // The user has stopped editing `urlTextBox`.
- // Reapply the highlighting.
- highlightUrlText();
- }
+ urlTextBox.setOnFocusChangeListener((View v, boolean hasFocus) -> {
+ if (hasFocus) { // The user is editing `urlTextBox`.
+ // Remove the highlighting.
+ urlTextBox.getText().removeSpan(redColorSpan);
+ urlTextBox.getText().removeSpan(initialGrayColorSpan);
+ urlTextBox.getText().removeSpan(finalGrayColorSpan);
+ } else { // The user has stopped editing `urlTextBox`.
+ // Reapply the highlighting.
+ highlightUrlText();
}
});
- // Set the `Go` button on the keyboard to load the URL in `urlTextBox`.
- urlTextBox.setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- // If the event is a key-down event on the `enter` button, load the URL.
- if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
- // Load the URL into the mainWebView and consume the event.
- try {
- loadUrlFromTextBox();
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- // If the enter key was pressed, consume the event.
- return true;
- } else {
- // If any other key was pressed, do not consume the event.
- return false;
+ // Set the go button on the keyboard to load the URL in `urlTextBox`.
+ urlTextBox.setOnKeyListener((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;
}
});
this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
// Get handles for views that need to be accessed.
- drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout);
- rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout);
- bookmarksListView = (ListView) findViewById(R.id.bookmarks_drawer_listview);
- bookmarksTitleTextView = (TextView) findViewById(R.id.bookmarks_title_textview);
- FloatingActionButton launchBookmarksActivityFab = (FloatingActionButton) findViewById(R.id.launch_bookmarks_activity_fab);
- FloatingActionButton createBookmarkFolderFab = (FloatingActionButton) findViewById(R.id.create_bookmark_folder_fab);
- FloatingActionButton createBookmarkFab = (FloatingActionButton) findViewById(R.id.create_bookmark_fab);
- mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.main_webview_relativelayout);
- mainWebView = (WebView) findViewById(R.id.main_webview);
- findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
- findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
- fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.full_screen_video_framelayout);
- urlAppBarRelativeLayout = (RelativeLayout) findViewById(R.id.url_app_bar_relativelayout);
- favoriteIconImageView = (ImageView) findViewById(R.id.favorite_icon);
+ drawerLayout = findViewById(R.id.drawerlayout);
+ rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
+ bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
+ bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
+ FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
+ FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
+ FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
+ mainWebViewRelativeLayout = findViewById(R.id.main_webview_relativelayout);
+ mainWebView = findViewById(R.id.main_webview);
+ findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+ findOnPageEditText = findViewById(R.id.find_on_page_edittext);
+ fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
+ urlAppBarRelativeLayout = findViewById(R.id.url_app_bar_relativelayout);
+ favoriteIconImageView = findViewById(R.id.favorite_icon);
// Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
if (darkTheme) {
}
// Set the launch bookmarks activity FAB to launch the bookmarks activity.
- launchBookmarksActivityFab.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Create an intent to launch the bookmarks activity.
- Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
+ launchBookmarksActivityFab.setOnClickListener(v -> {
+ // Create an intent to launch the bookmarks activity.
+ Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
- // Include the current folder with the `Intent`.
- bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
+ // Include the current folder with the `Intent`.
+ bookmarksIntent.putExtra("Current Folder", currentBookmarksFolder);
- // Make it so.
- startActivity(bookmarksIntent);
- }
+ // Make it so.
+ startActivity(bookmarksIntent);
});
// Set the create new bookmark folder FAB to display the `AlertDialog`.
- createBookmarkFolderFab.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
- AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
- createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder));
- }
+ createBookmarkFolderFab.setOnClickListener(v -> {
+ // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
+ AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
+ createBookmarkFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_folder));
});
// Set the create new bookmark FAB to display the `AlertDialog`.
- createBookmarkFab.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
- AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
- createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
- }
+ createBookmarkFab.setOnClickListener(view -> {
+ // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
+ AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
+ createBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.create_bookmark));
});
// Create a double-tap listener to toggle full-screen mode.
// Set `rootCoordinatorLayout` to fit under the status and navigation bars.
rootCoordinatorLayout.setFitsSystemWindows(false);
- if (translucentNavigationBarOnFullscreen) { // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
+ // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
+ if (translucentNavigationBarOnFullscreen) {
// Set the navigation bar to be translucent.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
});
// Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
- mainWebView.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- // Send the `event` to `gestureDetector`.
- return gestureDetector.onTouchEvent(event);
- }
+ mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
+ // Call `performClick()` on the view, which is required for accessibility.
+ v.performClick();
+
+ // Send the `event` to `gestureDetector`.
+ return gestureDetector.onTouchEvent(event);
});
// Update `findOnPageCountTextView`.
// `activeMatchOrdinal` is zero-based.
int activeMatch = activeMatchOrdinal + 1;
+ // Build the match string.
+ String matchString = activeMatch + "/" + numberOfMatches;
+
// Set `findOnPageCountTextView`.
- findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches);
+ findOnPageCountTextView.setText(matchString);
}
}
});
});
// 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);
+ findOnPageEditText.setOnKeyListener((v, keyCode, 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;
- }
+ // 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.swipe_refreshlayout);
+ swipeRefreshLayout = findViewById(R.id.swipe_refreshlayout);
swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
- swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
- @Override
- public void onRefresh() {
- mainWebView.reload();
- }
- });
+ swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
// `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
// Listen for touches on the navigation menu.
- final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationview);
+ final NavigationView navigationView = findViewById(R.id.navigationview);
navigationView.setNavigationItemSelectedListener(this);
// Get handles for `navigationMenu` and the back and forward menu items. The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu.
// Load the home folder, which is `""` in the database.
loadBookmarksFolder();
- bookmarksListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- // Convert the id from long to int to match the format of the bookmarks database.
- int databaseID = (int) id;
-
- // Get the bookmark `Cursor` for this ID and move it to the first row.
- Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
- bookmarkCursor.moveToFirst();
-
- // Act upon the bookmark according to the type.
- if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
- // Store the new folder name in `currentBookmarksFolder`.
- currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
-
- // Load the new folder.
- loadBookmarksFolder();
- } else { // The selected bookmark is not a folder.
- // Load the bookmark URL.
- loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
-
- // Close the bookmarks drawer.
- drawerLayout.closeDrawer(GravityCompat.END);
- }
+ bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
+ // Convert the id from long to int to match the format of the bookmarks database.
+ int databaseID = (int) id;
+
+ // Get the bookmark `Cursor` for this ID and move it to the first row.
+ Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
+ bookmarkCursor.moveToFirst();
+
+ // Act upon the bookmark according to the type.
+ if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
+ // Store the new folder name in `currentBookmarksFolder`.
+ currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+
+ // Load the new folder.
+ loadBookmarksFolder();
+ } else { // The selected bookmark is not a folder.
+ // Load the bookmark URL.
+ loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
- // Close the `Cursor`.
- bookmarkCursor.close();
+ // Close the bookmarks drawer.
+ drawerLayout.closeDrawer(GravityCompat.END);
}
- });
- bookmarksListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- // Convert the database ID from `long` to `int`.
- int databaseId = (int) id;
+ // Close the `Cursor`.
+ bookmarkCursor.close();
+ });
- // Find out if the selected bookmark is a folder.
- boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
+ bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
+ // Convert the database ID from `long` to `int`.
+ int databaseId = (int) id;
- if (isFolder) {
- // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
- oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
+ // Find out if the selected bookmark is a folder.
+ boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
- // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
- AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
- editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
- } else {
- // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
- AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
- editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
- }
+ if (isFolder) {
+ // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
+ oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
- // Consume the event.
- return true;
+ // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
+ AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
+ editFolderDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_folder));
+ } else {
+ // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
+ AppCompatDialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
+ editBookmarkDialog.show(getSupportFragmentManager(), getResources().getString(R.string.edit_bookmark));
}
+
+ // Consume the event.
+ return true;
});
// The `DrawerListener` allows us to update the Navigation Menu.
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
- public void onDrawerSlide(View drawerView, float slideOffset) {
+ public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
}
@Override
- public void onDrawerOpened(View drawerView) {
+ public void onDrawerOpened(@NonNull View drawerView) {
}
@Override
- public void onDrawerClosed(View drawerView) {
+ public void onDrawerClosed(@NonNull View drawerView) {
}
@Override
// drawerToggle creates the hamburger icon at the start of the AppBar.
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
- // Initialize `adServerSet`.
- final Set<String> adServersSet = new HashSet<>();
-
- // Load the list of ad servers into memory.
- try {
- // Load `pgl.yoyo.org_adservers.txt` into a `BufferedReader`.
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getAssets().open("pgl.yoyo.org_adservers.txt")));
-
- // Create a string for storing each ad server.
- String adServer;
-
- // Populate `adServersSet`.
- while ((adServer = bufferedReader.readLine()) != null) {
- adServersSet.add(adServer);
- }
-
- // Close `bufferedReader`.
- bufferedReader.close();
- } catch (IOException ioException) {
- // We're pretty sure the asset exists, so we don't need to worry about the `IOException` ever being thrown.
- }
-
mainWebView.setWebViewClient(new WebViewClient() {
// `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
// We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
@SuppressWarnings("deprecation")
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
- if (url.startsWith("mailto:")) { // Load the URL in an external email program because it begins with `mailto:`.
- // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
+ if (url.startsWith("mailto:")) { // Load the email address in an external email program.
+ // 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`.
// Make it so.
startActivity(emailIntent);
+ // Returning `true` indicates the application is handling the URL.
+ return true;
+ } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
+ // `ACTION_DIAL` open the dialer and loads the phone number, but waits for the user to place the call.
+ Intent dialIntent = new Intent(Intent.ACTION_DIAL);
+
+ // Add the phone number to the intent.
+ dialIntent.setData(Uri.parse(url));
+
+ // `FLAG_ACTIVITY_NEW_TASK` opens the dialer in a new task instead as part of Privacy Browser.
+ dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Make it so.
+ startActivity(dialIntent);
+
// Returning `true` indicates the application is handling the URL.
return true;
} else { // Load the URL in Privacy Browser.
}
}
- // Block ads. We have to use the deprecated `shouldInterceptRequest` until minimum API >= 21.
+ // Check requests against the block lists. The deprecated `shouldInterceptRequest` must be used until minimum API >= 21.
@SuppressWarnings("deprecation")
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url){
- if (adBlockerEnabled) { // Block ads.
- // Extract the host from `url`.
+ if (adBlockerEnabled) { // Check the block lists.
+ Log.i("BlockLists", "Begin check for " + url);
+
+ Uri currentUri = Uri.parse(formattedUrlString);
+ String currentDomain = currentUri.getHost();
+
Uri requestUri = Uri.parse(url);
- String requestHost = requestUri.getHost();
+ String requestDomain = requestUri.getHost();
+
+ boolean thirdPartyRequest = !requestDomain.equals(currentDomain);
+
+ WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
+
+ for (String whiteListEntry : mainWhiteList) {
+ if (url.contains(whiteListEntry)) {
+ Log.i("BlockLists", "Request allowed by main white list: " + whiteListEntry + " | " + url);
+
+ // `Return null` loads the requested resource.
+ return null;
+ }
+ }
+
+ for (String[] whiteListEntry : multiEntryWhiteList) {
+ if (whiteListEntry.length == 2) { // There are two entries.
+ if (url.contains(whiteListEntry[0]) && url.contains(whiteListEntry[1])) {
+ Log.i("BlockLists", "Request allowed by multi entry white list: " + whiteListEntry[0] + " , " + whiteListEntry[1] + " | " + url);
+
+ // `Return null` loads the requested resource.
+ return null;
+ }
+ } else { // There are three entries.
+ if (url.contains(whiteListEntry[0]) && url.contains(whiteListEntry[1]) && url.contains(whiteListEntry[2])) {
+ Log.i("BlockLists", "Request allowed by multi entry white list: " + whiteListEntry[0] + " , " + whiteListEntry[1] + " , " + whiteListEntry[2] + " | " + url);
+
+ // `Return null` loads the requested resource.
+ return null;
+ }
+ }
+ }
+
+ for (String[] whiteListEntry : domainWhiteList) {
+ if (requestDomain.endsWith(whiteListEntry[0]) && url.contains(whiteListEntry[1])) {
+ Log.i("BlockLists", "Request allowed by domain white list: " + whiteListEntry[0] + " , " + whiteListEntry[1] + " | " + url);
+
+ // `Return null` loads the requested resource.
+ return null;
+ }
+ }
+
+ // Only check the third-party white lists if this is a third-party request.
+ if (thirdPartyRequest) {
+ for (String[] whiteListEntry : thirdPartyDomainWhiteList) {
+ if (requestDomain.endsWith(whiteListEntry[0]) && url.contains(whiteListEntry[1])) {
+ Log.i("BlockLists", "Request allowed by third-party domain white list: " + whiteListEntry[0] + " , " + whiteListEntry[1] + " | " + url);
+
+ // `Return null` loads the requested resource.
+ return null;
+ }
+ }
+ }
+
+ for (String blockListEntry : mainBlockList) {
+ if (url.contains(blockListEntry)) {
+ Log.i("BlockLists", "Request blocked by main block list: " + blockListEntry + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String blockListEntry : initialBlockList) {
+ if (url.startsWith(blockListEntry)) {
+ Log.i("BlockLists", "Request blocked by initial block list: " + blockListEntry + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String blockListEntry : finalBlockList) {
+ if (url.endsWith(blockListEntry)) {
+ Log.i("BlockLists", "Request blocked by final block list: " + blockListEntry + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String[] blockListEntry : multiEntryBlockList) {
+ if (blockListEntry.length == 2) { // There are two entries.
+ if (url.contains(blockListEntry[0]) && url.contains(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by multi entry block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ } else { // There are three entries.
+ if (url.contains(blockListEntry[0]) && url.contains(blockListEntry[1]) && url.contains(blockListEntry[2])) {
+ Log.i("BlockLists", "Request blocked by multi entry block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " , " + blockListEntry[2] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+ }
+
+ for (String[] blockListEntry : multiEntryInitialBlockList) {
+ if (url.startsWith(blockListEntry[0]) && url.contains(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by multi entry initial block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String[] blockListEntry : multiEntryFinalBlockList) {
+ if (url.contains(blockListEntry[0]) && url.endsWith(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by multi entry final block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String[] blockListEntry : domainBlockList) {
+ if (blockListEntry.length == 2) { // There is one entry.
+ if (requestDomain.endsWith(blockListEntry[0]) && url.contains(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by domain block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ } else { // There are two entries.
+ if (requestDomain.endsWith(blockListEntry[0]) && url.contains(blockListEntry[1]) && url.contains(blockListEntry[2])) {
+ Log.i("BlockLists", "Request blocked by domain block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " , " + blockListEntry[2] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+ }
+
+ for (String[] blockListEntry : domainInitialBlockList) {
+ if (requestDomain.endsWith(blockListEntry[0]) && url.startsWith(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by domain initial block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String[] blockListEntry : domainFinalBlockList) {
+ if (requestDomain.endsWith(blockListEntry[0]) && url.endsWith(blockListEntry[2])) {
+ Log.i("BlockLists", "Request blocked by domain final block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String[] blockListEntry : domainRegularExpressionBlockList) {
+ if (requestDomain.endsWith(blockListEntry[0]) && Pattern.matches(blockListEntry[1], url)) {
+ Log.i("BlockLists", "Request blocked by domain regular expression block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ // Only check the third-party block lists if this is a third-party request.
+ if (thirdPartyRequest) {
+ for (String blockListEntry : thirdPartyBlockList) {
+ if (url.contains(blockListEntry)) {
+ Log.i("BlockLists", "Request blocked by third-party block list: " + blockListEntry + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String blockListEntry : thirdPartyInitialBlockList) {
+ if (url.startsWith(blockListEntry)) {
+ Log.i("BlockLists", "Request blocked by third-party initial block list: " + blockListEntry + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String[] blockListEntry : thirdPartyMultiEntryBlockList) {
+ switch (blockListEntry.length) {
+ case 2: // There are two entries.
+ if (url.contains(blockListEntry[0]) && url.contains(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by third-party multi-entry block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ break;
+
+ case 3: // There are three entries.
+ if (url.contains(blockListEntry[0]) && url.contains(blockListEntry[1]) && url.contains(blockListEntry[2])) {
+ Log.i("BlockLists", "Request blocked by third-party multi-entry block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " , " + blockListEntry[2] +
+ " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ break;
- // Initialize a variable to track if this is an ad server.
- boolean requestHostIsAdServer = false;
+ case 4: // There are four entries.
+ if (url.contains(blockListEntry[0]) && url.contains(blockListEntry[1]) && url.contains(blockListEntry[2]) && url.contains(blockListEntry[3])) {
+ Log.i("BlockLists", "Request blocked by third-party multi-entry block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " , " + blockListEntry[2] +
+ " , " + blockListEntry[3] + " | " + url);
- // Check all the subdomains of `requestHost` if it is not `null` against the ad server database.
- if (requestHost != null) {
- while (requestHost.contains(".") && !requestHostIsAdServer) { // Stop checking if we run out of `.` or if we already know that `requestHostIsAdServer` is `true`.
- if (adServersSet.contains(requestHost)) {
- requestHostIsAdServer = true;
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ break;
}
+ }
- // Strip out the lowest subdomain of `requestHost`.
- requestHost = requestHost.substring(requestHost.indexOf(".") + 1);
+ for (String[] blockListEntry : thirdPartyMultiEntryInitialBlockList) {
+ if (url.startsWith(blockListEntry[0]) && url.contains(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by third-party multi-entry initial block list");
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+
+ for (String[] blockListEntry : thirdPartyDomainBlockList) {
+ if (blockListEntry.length == 2) { // There is one entry.
+ if (requestDomain.endsWith(blockListEntry[0]) && url.contains(blockListEntry[1])) {
+ Log.i("BlockLists", "Request blocked by third-party domain block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ } else { // There are two entries.
+ if (requestDomain.endsWith(blockListEntry[0]) && url.contains(blockListEntry[1]) && url.contains(blockListEntry[2])) {
+ Log.i("BlockLists", "Request blocked by third-party domain block list: " + blockListEntry[0] + " , " + blockListEntry[1] + " , " + blockListEntry[2] + " | " +
+ url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
+ }
+ }
+
+ for (String blockListEntry : thirdPartyRegularExpressionBlockList) {
+ if (Pattern.matches(blockListEntry, url)) {
+ Log.i("BlockLists", "Request blocked by third-party regular expression block list: " + blockListEntry + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
}
}
- if (requestHostIsAdServer) { // It is an ad server.
- // Return an empty `WebResourceResponse`.
- return new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
- } else { // It is not an ad server.
- // `return null` loads the requested resource.
- return null;
+ for (String blockListEntry : regularExpressionBlockList) {
+ if (Pattern.matches(blockListEntry, url)) {
+ Log.i("BlockLists", "Request blocked by regular expression block list: " + blockListEntry + " | " + url);
+
+ // Return an empty `WebResourceResponse`.
+ return emptyWebResourceResponse;
+ }
}
+
+ Log.i("BlockLists", "End check for " + url);
+
+ // `return null` loads the requested resource.
+ return null;
} else { // Ad blocking is disabled.
// `return null` loads the requested resource.
return null;
}
// Check to see if the pinned SSL certificate matches the current website certificate.
- if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) || !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) ||
- !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) || !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
- !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) { // The pinned SSL certificate doesn't match the current domain certificate.
+ if (!currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) || !currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) ||
+ !currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) || !currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) ||
+ !currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) || !currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) ||
+ !currentWebsiteSslStartDateString.equals(pinnedDomainSslStartDateString) || !currentWebsiteSslEndDateString.equals(pinnedDomainSslEndDateString)) {
+ // The pinned SSL certificate doesn't match the current domain certificate.
//Display the pinned SSL certificate mismatch `AlertDialog`.
AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
// Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
if (pinnedDomainSslCertificate &&
- currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) && currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) &&
- currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) && currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
- currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) { // An SSL certificate is pinned and matches the current domain certificate.
+ currentWebsiteIssuedToCName.equals(pinnedDomainSslIssuedToCNameString) && currentWebsiteIssuedToOName.equals(pinnedDomainSslIssuedToONameString) &&
+ currentWebsiteIssuedToUName.equals(pinnedDomainSslIssuedToUNameString) && currentWebsiteIssuedByCName.equals(pinnedDomainSslIssuedByCNameString) &&
+ currentWebsiteIssuedByOName.equals(pinnedDomainSslIssuedByONameString) && currentWebsiteIssuedByUName.equals(pinnedDomainSslIssuedByUNameString) &&
+ currentWebsiteSslStartDate.equals(pinnedDomainSslStartDate) && currentWebsiteSslEndDate.equals(pinnedDomainSslEndDate)) {
+ // An SSL certificate is pinned and matches the current domain certificate.
// Proceed to the website without displaying an error.
handler.proceed();
} else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
});
// Get a handle for the progress bar.
- final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
+ final ProgressBar progressBar = findViewById(R.id.progress_bar);
mainWebView.setWebChromeClient(new WebChromeClient() {
// Update the progress bar when a page is loading.
public void onProgressChanged(WebView view, int progress) {
// Inject the night mode CSS if night mode is enabled.
if (nightMode) {
- // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links used by WordPress.
- // `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color. `border: none` removes all borders, which can also be used to underline text.
+ // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links
+ // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
+ // `border: none` removes all borders, which can also be used to underline text.
// `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings.
- mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = '" +
- "* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important; text-shadow: none !important; border: none !important;}" +
- "a {color: #1565C0 !important;}" +
- "'; parent.appendChild(style)})()", new ValueCallback<String>() {
- @Override
- public void onReceiveValue(String value) {
- // Initialize a `Handler` to display `mainWebView`.
- Handler displayWebViewHandler = new Handler();
-
- // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied.
- Runnable displayWebViewRunnable = new Runnable() {
- public void run() {
+ mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
+ "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
+ "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;}'; parent.appendChild(style)})()", value -> {
+ // Initialize a `Handler` to display `mainWebView`.
+ Handler displayWebViewHandler = new Handler();
+
+ // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied.
+ Runnable displayWebViewRunnable = () -> {
// Only display `mainWebView` if the progress bar is one. This prevents the display of the `WebView` while it is still loading.
if (progressBar.getVisibility() == View.GONE) {
mainWebView.setVisibility(View.VISIBLE);
}
- }
- };
+ };
- // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds.
- displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
- }
- });
+ // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 500 milliseconds.
+ displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
+ });
}
+ // Update the progress bar.
progressBar.setProgress(progress);
+
+ // Set the visibility of the progress bar.
if (progress < 100) {
// Show the progress bar.
progressBar.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
// Display `mainWebView` if night mode is disabled.
- // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not currently enabled.
+ // Because of a race condition between `applyDomainSettings` and `onPageStarted`, when night mode is set by domain settings the `WebView` may be hidden even if night mode is not
+ // currently enabled.
if (!nightMode) {
mainWebView.setVisibility(View.VISIBLE);
}
registerForContextMenu(mainWebView);
// Allow the downloading of files.
- mainWebView.setDownloadListener(new DownloadListener() {
- @Override
- public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
- // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`.
- AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
- downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
- }
+ mainWebView.setDownloadListener((String url, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
+ // Show the `DownloadFileDialog` `AlertDialog` and name this instance `@string/download`.
+ AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+ downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
});
// Allow pinch to zoom.
privacyBrowserRuntime = Runtime.getRuntime();
// Store the application's private data directory.
- privateDataDirectoryString = getApplicationInfo().dataDir; // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
+ privateDataDirectoryString = getApplicationInfo().dataDir;
+ // `dataDir` will vary, but will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
// Initialize `inFullScreenBrowsingMode`, which is always false at this point because Privacy Browser never starts in full screen browsing mode.
inFullScreenBrowsingMode = false;
// Initialize `webViewTitle`.
webViewTitle = getString(R.string.no_title);
- // Initialize `favoriteIconBitmap`. We have to use `ContextCompat` until API >= 21.
+ // Initialize `favoriteIconBitmap`. `ContextCompat` must be used until API >= 21.
Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
+ assert favoriteIconBitmapDrawable != null;
favoriteIconDefaultBitmap = favoriteIconBitmapDrawable.getBitmap();
// If the favorite icon is null, load the default.
@Override
public void onRestart() {
+ // Run the default commands.
super.onRestart();
// Apply the app settings, which may have been changed in `SettingsActivity`.
applyAppSettings();
+ // Apply the domain settings if returning from the Domains Activity.
+ if (reapplyDomainSettingsOnRestart) {
+ // Reset `reapplyDomainSettingsOnRestart`.
+ reapplyDomainSettingsOnRestart = false;
+
+ // Reapply the domain settings.
+ applyDomainSettings(formattedUrlString);
+ }
+
// Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
updatePrivacyIcons(true);
// `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
@Override
public void onResume() {
+ // Run the default commands.
super.onResume();
// Resume JavaScript (if enabled).
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// Get handles for the menu items.
+ MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data);
+ MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data);
MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
+ // Set the text for the domain menu item.
+ if (domainSettingsApplied) {
+ addOrEditDomain.setTitle(R.string.edit_domain_settings);
+ } else {
+ addOrEditDomain.setTitle(R.string.add_domain_settings);
+ }
+
// Set the status of the menu item checkboxes.
toggleFirstPartyCookiesMenuItem.setChecked(firstPartyCookiesEnabled);
toggleThirdPartyCookiesMenuItem.setChecked(thirdPartyCookiesEnabled);
WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
clearFormDataMenuItem.setEnabled(mainWebViewDatabase.hasFormData());
+ // Enable `Clear Data` if any of the submenu items are enabled.
+ clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
+
// Initialize font size variables.
int fontSize = mainWebView.getSettings().getTextZoom();
String fontSizeTitle;
// Run all the other default commands.
super.onPrepareOptionsMenu(menu);
- // `return true` displays the menu.
+ // Display the menu.
return true;
}
// removeAllCookies is deprecated, but it is required for API < 21.
@SuppressWarnings("deprecation")
public boolean onOptionsItemSelected(MenuItem menuItem) {
+ // Get the selected menu item ID.
int menuItemId = menuItem.getItemId();
// Set the commands that relate to the menu entries.
switch (menuItemId) {
+ case R.id.add_or_edit_domain:
+ if (domainSettingsApplied) { // Edit the current domain settings.
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
+ currentDomainName = "";
+
+ // Create an intent to launch the domains activity.
+ Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+ // Put extra information instructing the domains activity to directly load the current domain.
+ domainsIntent.putExtra("LoadDomain", domainSettingsDatabaseId);
+
+ // Make it so.
+ startActivity(domainsIntent);
+ } else { // Add a new domain.
+ // Show the add domain `AlertDialog`.
+ AppCompatDialogFragment addDomainDialog = new AddDomainDialog();
+ addDomainDialog.show(getSupportFragmentManager(), getResources().getString(R.string.add_domain));
+ }
+ return true;
+
case R.id.toggle_javascript:
// Switch the status of javaScriptEnabled.
javaScriptEnabled = !javaScriptEnabled;
case R.id.clear_cookies:
Snackbar.make(findViewById(R.id.main_webview), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
- .setAction(R.string.undo, new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Do nothing because everything will be handled by `onDismissed()` below.
- }
+ .setAction(R.string.undo, v -> {
+ // Do nothing because everything will be handled by `onDismissed()` below.
})
.addCallback(new Snackbar.Callback() {
@Override
case R.id.clear_dom_storage:
Snackbar.make(findViewById(R.id.main_webview), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
- .setAction(R.string.undo, new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Do nothing because everything will be handled by `onDismissed()` below.
- }
+ .setAction(R.string.undo, v -> {
+ // Do nothing because everything will be handled by `onDismissed()` below.
})
.addCallback(new Snackbar.Callback() {
@Override
WebStorage webStorage = WebStorage.getInstance();
webStorage.deleteAllData();
- // Manually remove `IndexedDB` if it exists.
+ // Manually delete the DOM storage files and directories.
try {
+ // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+ privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
+
+ // Multiple commands must be used because `Runtime.exec()` does not like `*`.
privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+ privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+ privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+ privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
} catch (IOException e) {
// Do nothing if an error is thrown.
}
case R.id.clear_form_data:
Snackbar.make(findViewById(R.id.main_webview), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
- .setAction(R.string.undo, new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Do nothing because everything will be handled by `onDismissed()` below.
- }
+ .setAction(R.string.undo, v -> {
+ // Do nothing because everything will be handled by `onDismissed()` below.
})
.addCallback(new Snackbar.Callback() {
@Override
// Show the Find on Page `RelativeLayout`.
findOnPageLinearLayout.setVisibility(View.VISIBLE);
- // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android. http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
- findOnPageEditText.postDelayed(new Runnable() {
- @Override
- public void run()
- {
- // Set the focus on `findOnPageEditText`.
- findOnPageEditText.requestFocus();
+ // Display the keyboard. 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(() -> {
+ // Set the focus on `findOnPageEditText`.
+ findOnPageEditText.requestFocus();
- // Display the keyboard. `0` sets no input flags.
- inputMethodManager.showSoftInput(findOnPageEditText, 0);
- }
+ // Display the keyboard. `0` sets no input flags.
+ inputMethodManager.showSoftInput(findOnPageEditText, 0);
}, 200);
return true;
// Convert `mainWebView` to `printDocumentAdapter`.
PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
+ // Remove the lint error below that `printManager` might be `null`.
+ assert printManager != null;
+
// Print the document. The print attributes are `null`.
printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
return true;
+ case R.id.view_source:
+ // Launch the Vew Source activity.
+ Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
+ startActivity(viewSourceIntent);
+ return true;
+
case R.id.add_to_homescreen:
// Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
break;
case R.id.domains:
- // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
currentDomainName = "";
// Launch `DomainsActivity`.
break;
case R.id.settings:
- // Reset `currentDomainName` so that domain settings are reapplied after returning to `MainWebViewActivity`.
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
currentDomainName = "";
// Launch `SettingsActivity`.
// Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
try {
- // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+ // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
- // We have to use multiple commands because `Runtime.exec()` does not like `*`.
+ // Multiple commands must be used because `Runtime.exec()` does not like `*`.
privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
try {
// Delete the main cache directory.
privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
-
- // Delete the secondary `Service Worker` cache directory. We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
+ // Delete the secondary `Service Worker` cache directory.
+ // We have to use a `String[]` because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
} catch (IOException e) {
// Do nothing if an error is thrown.
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
+ // `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);
}
// Get a handle for the `ClipboardManager`.
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ // Remove the lint errors below that `clipboardManager` might be `null`.
+ assert clipboardManager != null;
+
switch (hitTestResult.getType()) {
// `SRC_ANCHOR_TYPE` is a link.
case WebView.HitTestResult.SRC_ANCHOR_TYPE:
menu.setHeaderTitle(linkUrl);
// Add a `Load URL` entry.
- menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- loadUrl(linkUrl);
- return false;
- }
+ menu.add(R.string.load_url).setOnMenuItemClickListener(item -> {
+ loadUrl(linkUrl);
+ return false;
});
// Add a `Copy URL` entry.
- menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // Save the link URL in a `ClipData`.
- ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
-
- // Set the `ClipData` as the clipboard's primary clip.
- clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
- return false;
- }
+ menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+ // Save the link URL in a `ClipData`.
+ ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
+
+ // Set the `ClipData` as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
+ return false;
});
// Add a `Cancel` entry, which by default closes the `ContextMenu`.
menu.setHeaderTitle(linkUrl);
// Add a `Write Email` entry.
- menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
- Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
+ menu.add(R.string.write_email).setOnMenuItemClickListener(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));
+ // 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);
+ // `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;
- }
+ // Make it so.
+ startActivity(emailIntent);
+ return false;
});
// Add a `Copy Email Address` entry.
- menu.add(R.string.copy_email_address).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // Save the email address in a `ClipData`.
- ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
-
- // Set the `ClipData` as the clipboard's primary clip.
- clipboardManager.setPrimaryClip(srcEmailTypeClipData);
- return false;
- }
+ menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
+ // Save the email address in a `ClipData`.
+ ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
+
+ // Set the `ClipData` as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(srcEmailTypeClipData);
+ return false;
});
// Add a `Cancel` entry, which by default closes the `ContextMenu`.
menu.setHeaderTitle(imageUrl);
// Add a `View Image` entry.
- menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- loadUrl(imageUrl);
- return false;
- }
+ menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+ loadUrl(imageUrl);
+ return false;
});
// Add a `Download Image` entry.
- menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
- AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
- downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
- return false;
- }
+ menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+ // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
+ AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+ downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ return false;
});
// Add a `Copy URL` entry.
- menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // Save the image URL in a `ClipData`.
- ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
-
- // Set the `ClipData` as the clipboard's primary clip.
- clipboardManager.setPrimaryClip(srcImageTypeClipData);
- return false;
- }
+ menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+ // Save the image URL in a `ClipData`.
+ ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+
+ // Set the `ClipData` as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(srcImageTypeClipData);
+ return false;
});
// Add a `Cancel` entry, which by default closes the `ContextMenu`.
menu.setHeaderTitle(imageUrl);
// Add a `View Image` entry.
- menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- loadUrl(imageUrl);
- return false;
- }
+ menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
+ loadUrl(imageUrl);
+ return false;
});
// Add a `Download Image` entry.
- menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
- AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
- downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
- return false;
- }
+ menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
+ // Show the `DownloadImageDialog` `AlertDialog` and name this instance `@string/download`.
+ AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+ downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ return false;
});
// Add a `Copy URL` entry.
- menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- // Save the image URL in a `ClipData`.
- ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
-
- // Set the `ClipData` as the clipboard's primary clip.
- clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
- return false;
- }
+ menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
+ // Save the image URL in a `ClipData`.
+ ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
+
+ // Set the `ClipData` as the clipboard's primary clip.
+ clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
+ return false;
});
// Add a `Cancel` entry, which by default closes the `ContextMenu`.
}
}
+ @Override
+ public void onAddDomain(AppCompatDialogFragment dialogFragment) {
+ // Reapply the domain settings on returning to `MainWebViewActivity`.
+ reapplyDomainSettingsOnRestart = true;
+ currentDomainName = "";
+
+ // Get the new domain name `String` from `dialogFragment`.
+ EditText domainNameEditText = dialogFragment.getDialog().findViewById(R.id.domain_name_edittext);
+ String domainNameString = domainNameEditText.getText().toString();
+
+ // Initialize the database handler. `this` specifies the context. The two `nulls` do not specify the database name or a `CursorFactory`.
+ // The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
+ DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
+
+ // Create the domain and store the database ID in `currentDomainDatabaseId`.
+ int newDomainDatabaseId = domainsDatabaseHelper.addDomain(domainNameString);
+
+ // Create an intent to launch the domains activity.
+ Intent domainsIntent = new Intent(this, DomainsActivity.class);
+
+ // Put extra information instructing the domains activity to directly load the current domain.
+ domainsIntent.putExtra("LoadDomain", newDomainDatabaseId);
+
+ // Make it so.
+ startActivity(domainsIntent);
+ }
+
@Override
public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
// Get the `EditTexts` from the `dialogFragment`.
- EditText createBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
- EditText createBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
+ EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
+ EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
// Extract the strings from the `EditTexts`.
String bookmarkNameString = createBookmarkNameEditText.getText().toString();
int newBookmarkDisplayOrder = bookmarksListView.getCount();
// Create the bookmark.
- bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, newBookmarkDisplayOrder, currentBookmarksFolder, favoriteIconByteArray);
+ bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
// Update `bookmarksCursor` with the current contents of this folder.
bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
@Override
public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
// Get handles for the views in `dialogFragment`.
- EditText createFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
- RadioButton defaultFolderIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
- ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
+ EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
+ RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
+ ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
// Get new folder name string.
String folderNameString = createFolderNameEditText.getText().toString();
bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
}
- // Create the folder, placing it at the top of the ListView
- bookmarksDatabaseHelper.createFolder(folderNameString, 0, currentBookmarksFolder, folderIconByteArray);
+ // Create the folder, which will be placed at the top of the `ListView`.
+ bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
// Update `bookmarksCursor` with the current contents of this folder.
bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
@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", favoriteIconBitmap);
- placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
- sendBroadcast(placeBookmarkShortcut);
+ // Get the shortcut name.
+ EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
+ String shortcutNameString = shortcutNameEditText.getText().toString();
+
+ // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
+ IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
+
+ // Setup the shortcut intent.
+ Intent shortcutIntent = new Intent();
+ shortcutIntent.setAction(Intent.ACTION_VIEW);
+ shortcutIntent.setData(Uri.parse(formattedUrlString));
+
+ // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
+ ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
+
+ // Add the required fields to the shortcut info builder.
+ shortcutInfoBuilder.setIcon(favoriteIcon);
+ shortcutInfoBuilder.setIntent(shortcutIntent);
+ shortcutInfoBuilder.setShortLabel(shortcutNameString);
+
+ // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
+ ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
}
@Override
}
// Get the file name from `dialogFragment`.
- EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name);
+ EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
String imageName = downloadImageNameEditText.getText().toString();
// Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
// Show the download notification after the download is completed.
downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ // Remove the lint warning below that `downloadManager` might be `null`.
+ assert downloadManager != null;
+
// Initiate the download.
downloadManager.enqueue(downloadRequest);
} else { // The image is not an HTTP or HTTPS URI.
}
// Get the file name from `dialogFragment`.
- EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
+ EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
String fileName = downloadFileNameEditText.getText().toString();
// Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
// Show the download notification after the download is completed.
downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ // Remove the lint warning below that `downloadManager` might be `null`.
+ assert downloadManager != null;
+
// Initiate the download.
downloadManager.enqueue(downloadRequest);
} else { // The download is not an HTTP or HTTPS URI.
}
@Override
- public void onSaveEditBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
+ public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
// Get handles for the views from `dialogFragment`.
- EditText editBookmarkNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
- EditText editBookmarkUrlEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
- RadioButton currentBookmarkIconRadioButton = (RadioButton) dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
+ EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
+ EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
+ RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
// Store the bookmark strings.
String bookmarkNameString = editBookmarkNameEditText.getText().toString();
}
@Override
- public void onSaveEditBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
+ public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
// Get handles for the views from `dialogFragment`.
- EditText editFolderNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
- 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);
- ImageView folderIconImageView = (ImageView) dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon);
+ EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
+ RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
+ RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
+ ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
// Get the new folder name.
String newFolderNameString = editFolderNameEditText.getText().toString();
@Override
public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
// Get handles for the `EditTexts`.
- EditText usernameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
- EditText passwordEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
+ EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
+ EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
// Proceed with the HTTP authentication.
httpAuthHandler.proceed(usernameEditText.getText().toString(), passwordEditText.getText().toString());
formattedUrlString = searchURL + encodedUrlString;
}
+ // Clear the focus from the URL text box. Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
+ urlTextBox.clearFocus();
+
loadUrl(formattedUrlString);
}
}
private void applyAppSettings() {
- // Get a handle for `sharedPreferences`. `this` references the current context.
+ // Get a handle for the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- // Store the values from `sharedPreferences` in variables.
+ // Store the values from the shared preferences in variables.
String homepageString = sharedPreferences.getString("homepage", "https://start.duckduckgo.com");
String torHomepageString = sharedPreferences.getString("tor_homepage", "https://3g2upl4pq6kufc4m.onion");
String torSearchString = sharedPreferences.getString("tor_search", "https://3g2upl4pq6kufc4m.onion/html/?q=");
cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
}
- // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. <https://redmine.stoutner.com/issues/160>
+ // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload.
+ // <https://redmine.stoutner.com/issues/160>
if (!urlIsLoading) {
switch (userAgentString) {
case "System default user agent":
// Use the selected user agent.
mainWebView.getSettings().setUserAgentString(userAgentString);
}
+
+ // Store the applied user agent string.
+ appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
}
// Set a green background on `urlTextBox` to indicate that custom domain settings are being used. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
}
- // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload. <https://redmine.stoutner.com/issues/160>
+ // Only set the user agent if the webpage is not currently loading. Otherwise, changing the user agent on redirects can cause the original website to reload.
+ // <https://redmine.stoutner.com/issues/160>
if (!urlIsLoading) {
switch (defaultUserAgentString) {
case "WebView default user agent":
// Use the selected user agent.
mainWebView.getSettings().setUserAgentString(defaultUserAgentString);
}
+
+ // Store the applied user agent string.
+ appliedUserAgentString = mainWebView.getSettings().getUserAgentString();
}
// Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
}
}
- // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity.
+ // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
if (runInvalidateOptionsMenu) {
- ActivityCompat.invalidateOptionsMenu(this);
+ invalidateOptionsMenu();
}
}
private void highlightUrlText() {
String urlString = urlTextBox.getText().toString();
- if (urlString.startsWith("http://")) { // Highlight connections that are not encrypted.
+ if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else if (urlString.startsWith("https://")) { // Highlight connections that are encrypted.
+ } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// Get handles for the views.
- ImageView bookmarkFavoriteIcon = (ImageView) view.findViewById(R.id.bookmark_favorite_icon);
- TextView bookmarkNameTextView = (TextView) view.findViewById(R.id.bookmark_name);
+ ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
+ TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
// Get the favorite icon byte array from the `Cursor`.
byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));