/*
- * Copyright © 2015-2018 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
*
* Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
*
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.DialogFragment;
import android.app.DownloadManager;
import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.net.Uri;
import android.net.http.SslCertificate;
import android.net.http.SslError;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
-import android.support.annotation.NonNull;
-import android.support.design.widget.CoordinatorLayout;
-import android.support.design.widget.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.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarDrawerToggle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.app.AppCompatDialogFragment;
-import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.Spanned;
import android.text.TextWatcher;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
+import android.webkit.WebSettings;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.ActionBarDrawerToggle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.GravityCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+import com.google.android.material.navigation.NavigationView;
+import com.google.android.material.snackbar.Snackbar;
+
import com.stoutner.privacybrowser.BuildConfig;
import com.stoutner.privacybrowser.R;
import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
-import com.stoutner.privacybrowser.dialogs.PinnedSslCertificateMismatchDialog;
+import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
import com.stoutner.privacybrowser.helpers.AdHelper;
import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
+import com.stoutner.privacybrowser.views.NestedScrollWebView;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.lang.ref.WeakReference;
+import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
// AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
- CreateHomeScreenShortcutDialog.CreateHomeScreenShortcutListener, DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener,
- DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener,
- HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedSslCertificateMismatchDialog.PinnedSslCertificateMismatchListener,
+ DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
+ EditBookmarkFolderDialog.EditBookmarkFolderListener, HttpAuthenticationDialog.HttpAuthenticationListener, NavigationView.OnNavigationItemSelectedListener, PinnedMismatchDialog.PinnedMismatchListener,
SslCertificateErrorDialog.SslCertificateErrorListener, UrlHistoryDialog.UrlHistoryListener {
// `darkTheme` is public static so it can be accessed from everywhere.
// `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()`.
+ // `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onCreateHomeScreenShortcut()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `applyDomainSettings()`.
public static Bitmap favoriteIconBitmap;
- // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`, `CreateBookmarkDialog`, and `AddDomainDialog`.
+ // `favoriteIconDefaultBitmap` public static so it can be accessed from `PinnedMismatchDialog`. It is also used in `onCreate()` and `applyDomainSettings`.
+ public static Bitmap favoriteIconDefaultBitmap;
+
+ // `formattedUrlString` is public static so it can be accessed from `AddDomainDialog`, `BookmarksActivity`, `DomainSettingsFragment`, `CreateBookmarkDialog`,
+ // and `PinnedMismatchDialog`.
// It is also used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onCreateHomeScreenShortcutCreate()`, `loadUrlFromTextBox()`, and `applyProxyThroughOrbot()`.
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`, `PinnedMismatchDialog`, and `ViewSslCertificateDialog`.
+ // It is also used in `onCreate()` and `checkPinnedMismatch()`.
public static SslCertificate sslCertificate;
+ // `currentHostIpAddresses` is public static so it can be accessed from `DomainSettingsFragment` and `ViewSslCertificateDialog`.
+ // It is also used in `onCreate()` and `GetHostIpAddresses()`.
+ public static String currentHostIpAddresses;
+
// `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
public static String orbotStatus;
- // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog` and `CreateHomeScreenShortcutDialog`. It is also used in `onCreate()`.
+ // `webViewTitle` is public static so it can be accessed from `CreateBookmarkDialog`. 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()`.
// `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
public static String currentBookmarksFolder;
- // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedSslCertificateMismatchDialog`. It is also used in `onCreate()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
+ // `domainSettingsDatabaseId` is public static so it can be accessed from `PinnedMismatchDialog`. 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;
- public static String pinnedDomainSslIssuedByCNameString;
- public static String pinnedDomainSslIssuedByONameString;
- public static String pinnedDomainSslIssuedByUNameString;
- public static Date pinnedDomainSslStartDate;
- public static Date pinnedDomainSslEndDate;
+ // The pinned variables are public static so they can be accessed from `PinnedMismatchDialog`. They are also used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
+ public static String pinnedSslIssuedToCName;
+ public static String pinnedSslIssuedToOName;
+ public static String pinnedSslIssuedToUName;
+ public static String pinnedSslIssuedByCName;
+ public static String pinnedSslIssuedByOName;
+ public static String pinnedSslIssuedByUName;
+ public static Date pinnedSslStartDate;
+ public static Date pinnedSslEndDate;
+ public static String pinnedHostIpAddresses;
// The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
public final static int UNRECOGNIZED_USER_AGENT = -1;
public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
- // `appBar` is used in `onCreate()`, `onOptionsItemSelected()`, `closeFindOnPage()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
- private ActionBar appBar;
- // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
- private boolean navigatingHistory;
+ // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, `applyDomainSettings()`, and `GetHostIpAddresses`.
+ private static boolean urlIsLoading;
+
+ // `gettingIpAddresses` is used in `onCreate() and `GetHostIpAddresses`.
+ private static boolean gettingIpAddresses;
+
+ // `pinnedDomainSslCertificate` is used in `onCreate()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
+ private static boolean pinnedSslCertificate;
+
+ // `pinnedIpAddress` is used in `applyDomainSettings()` and `checkPinnedMismatch()`.
+ private static boolean pinnedIpAddresses;
- // `favoriteIconDefaultBitmap` is used in `onCreate()` and `applyDomainSettings`.
- private Bitmap favoriteIconDefaultBitmap;
+ // `ignorePinnedDomainInformation` is used in `onSslMismatchProceed()`, `applyDomainSettings()`, and `checkPinnedMismatch()`.
+ private static boolean ignorePinnedDomainInformation;
- // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, `onBackPressed()`, and `onRestart()`.
- private DrawerLayout drawerLayout;
+ // The fragment manager is initialized in `onCreate()` and accessed from the static `checkPinnedMismatch()`.
+ private static FragmentManager fragmentManager;
- // `rootCoordinatorLayout` is used in `onCreate()` and `applyAppSettings()`.
- private CoordinatorLayout rootCoordinatorLayout;
+
+ // `navigatingHistory` is used in `onCreate()`, `onNavigationItemSelected()`, `onSslMismatchBack()`, and `applyDomainSettings()`.
+ private boolean navigatingHistory;
// `mainWebView` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
// `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, and `applyProxyThroughOrbot()`.
- private WebView mainWebView;
+ private NestedScrollWebView mainWebView;
// `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
private FrameLayout fullScreenVideoFrameLayout;
- // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `onRestart()`.
- private SwipeRefreshLayout swipeRefreshLayout;
-
// `urlAppBarRelativeLayout` is used in `onCreate()` and `applyDomainSettings()`.
private RelativeLayout urlAppBarRelativeLayout;
// `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
private boolean inFullScreenBrowsingMode;
- // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
- private boolean hideSystemBarsOnFullscreen;
-
- // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applyAppSettings()`.
- private boolean translucentNavigationBarOnFullscreen;
+ // Hide app bar is used in `onCreate()` and `applyAppSettings()`.
+ private boolean hideAppBar;
// `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()`.
- private boolean ignorePinnedSslCertificate;
-
// `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
private BroadcastReceiver orbotStatusBroadcastReceiver;
// `privateDataDirectoryString` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`.
private String privateDataDirectoryString;
- // `findOnPageLinearLayout` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
- private LinearLayout findOnPageLinearLayout;
-
// `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
private EditText findOnPageEditText;
// `displayAdditionalAppBarIcons` is used in `onCreate()` and `onCreateOptionsMenu()`.
private boolean displayAdditionalAppBarIcons;
- // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
- private ActionBarDrawerToggle drawerToggle;
-
- // `supportAppBar` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
- private Toolbar supportAppBar;
+ // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
+ private ActionBarDrawerToggle actionBarDrawerToggle;
// `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, `loadUrl()`, and `highlightUrlText()`.
private EditText urlTextBox;
// `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
private InputMethodManager inputMethodManager;
- // `mainWebViewRelativeLayout` is used in `onCreate()` and `onNavigationItemSelected()`.
- private RelativeLayout mainWebViewRelativeLayout;
-
- // `urlIsLoading` is used in `onCreate()`, `onCreateOptionsMenu()`, `loadUrl()`, and `applyDomainSettings()`.
- private boolean urlIsLoading;
-
- // `pinnedDomainSslCertificate` is used in `onCreate()` and `applyDomainSettings()`.
- private boolean pinnedDomainSslCertificate;
-
// `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
// and `loadBookmarksFolder()`.
private BookmarksDatabaseHelper bookmarksDatabaseHelper;
super.onCreate(savedInstanceState);
// Set the content view.
- setContentView(R.layout.main_drawerlayout);
+ setContentView(R.layout.main_framelayout);
- // Get a handle for the resources.
+ // Get handles for views, resources, and managers.
Resources resources = getResources();
-
- // Get a handle for `inputMethodManager`.
+ fragmentManager = getSupportFragmentManager();
inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ Toolbar toolbar = findViewById(R.id.toolbar);
- // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
- supportAppBar = findViewById(R.id.app_bar);
- setSupportActionBar(supportAppBar);
- appBar = getSupportActionBar();
+ // Set the action bar. `SupportActionBar` must be used until the minimum API is >= 21.
+ setSupportActionBar(toolbar);
+ ActionBar actionBar = getSupportActionBar();
- // This is needed to get rid of the Android Studio warning that `appBar` might be null.
- assert appBar != null;
+ // This is needed to get rid of the Android Studio warning that the action bar might be null.
+ assert actionBar != null;
// Add the custom `url_app_bar` layout, which shows the favorite icon and the URL text bar.
- appBar.setCustomView(R.layout.url_app_bar);
- appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+ actionBar.setCustomView(R.layout.url_app_bar);
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
// Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
redColorSpan = new ForegroundColorSpan(resources.getColor(R.color.red_a700));
// Register `orbotStatusBroadcastReceiver` on `this` context.
this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
- // Get handles for views that need to be accessed.
- drawerLayout = findViewById(R.id.drawerlayout);
- rootCoordinatorLayout = findViewById(R.id.root_coordinatorlayout);
+ // Get handles for views that need to be modified.
+ FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+ DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+ RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
+ SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+ mainWebView = findViewById(R.id.main_webview);
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);
// Set the create new bookmark folder FAB to display an alert dialog.
createBookmarkFolderFab.setOnClickListener(v -> {
- // Show the `CreateBookmarkFolderDialog` `AlertDialog` and name the instance `@string/create_folder`.
- AppCompatDialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
- createBookmarkFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_folder));
+ // Show the create bookmark folder dialog and name the instance `@string/create_folder`.
+ DialogFragment createBookmarkFolderDialog = new CreateBookmarkFolderDialog();
+ createBookmarkFolderDialog.show(fragmentManager, resources.getString(R.string.create_folder));
});
// Set the create new bookmark FAB to display an alert dialog.
createBookmarkFab.setOnClickListener(view -> {
- // Show the `CreateBookmarkDialog` `AlertDialog` and name the instance `@string/create_bookmark`.
- AppCompatDialogFragment createBookmarkDialog = new CreateBookmarkDialog();
- createBookmarkDialog.show(getSupportFragmentManager(), resources.getString(R.string.create_bookmark));
+ // Show the create bookmark dialog and name the instance `@string/create_bookmark`.
+ DialogFragment createBookmarkDialog = new CreateBookmarkDialog();
+ createBookmarkDialog.show(fragmentManager, resources.getString(R.string.create_bookmark));
});
// Create a double-tap listener to toggle full-screen mode.
@Override
public boolean onDoubleTap(MotionEvent event) {
if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
- // Toggle `inFullScreenBrowsingMode`.
+ // Toggle the full screen browsing mode tracker.
inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
+ // Toggle the full screen browsing mode.
if (inFullScreenBrowsingMode) { // Switch to full screen mode.
- // Hide the `appBar`.
- appBar.hide();
+ // Hide the app bar if specified.
+ if (hideAppBar) {
+ actionBar.hide();
+ }
// Hide the banner ad in the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
- // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
AdHelper.hideAd(findViewById(R.id.adview));
}
- // Modify the system bars.
- if (hideSystemBarsOnFullscreen) { // Hide everything.
- // Remove the translucent overlays.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
- // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
- drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-
- /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
- rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
- // Set `rootCoordinatorLayout` to fill the whole screen.
- rootCoordinatorLayout.setFitsSystemWindows(false);
- } else { // Hide everything except the status and navigation bars.
- // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
- rootCoordinatorLayout.setFitsSystemWindows(false);
-
- // 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);
- }
- }
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
} else { // Switch to normal viewing mode.
- // Show the `appBar`.
- appBar.show();
+ // Show the app bar.
+ actionBar.show();
- // Show the `BannerAd` in the free flavor.
+ // Show the banner ad in the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+ // Reload the ad.
AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
}
- // Remove the translucent navigation bar flag if it is set.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ // Remove the `SYSTEM_UI` flags from the root frame layout.
+ rootFrameLayout.setSystemUiVisibility(0);
- // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
+ // Add the translucent status flag.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
- // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
- rootCoordinatorLayout.setSystemUiVisibility(0);
-
- // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
- rootCoordinatorLayout.setFitsSystemWindows(true);
}
// Consume the double-tap.
});
// Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
- mainWebView.setOnTouchListener((View v, MotionEvent event) -> {
+ mainWebView.setOnTouchListener((View view, MotionEvent event) -> {
// Call `performClick()` on the view, which is required for accessibility.
- v.performClick();
+ view.performClick();
// Send the `event` to `gestureDetector`.
return gestureDetector.onTouchEvent(event);
// Update `findOnPageCountTextView`.
mainWebView.setFindListener(new WebView.FindListener() {
// Get a handle for `findOnPageCountTextView`.
- final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
+ final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
@Override
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
});
// Implement swipe to refresh.
- swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
swipeRefreshLayout.setOnRefreshListener(() -> mainWebView.reload());
+ // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
+ swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayout.getProgressViewStartOffset() - 10, swipeRefreshLayout.getProgressViewEndOffset());
+
// Set the swipe to refresh color according to the theme.
if (darkTheme) {
swipeRefreshLayout.setColorSchemeResources(R.color.blue_600);
int databaseID = (int) id;
// Get the bookmark cursor for this ID and move it to the first row.
- Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmarkCursor(databaseID);
+ Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
bookmarkCursor.moveToFirst();
// Act upon the bookmark according to the type.
oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
// Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
- AppCompatDialogFragment editFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
- editFolderDialog.show(getSupportFragmentManager(), resources.getString(R.string.edit_folder));
+ DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId);
+ editBookmarkFolderDialog.show(fragmentManager, resources.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(), resources.getString(R.string.edit_bookmark));
+ DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId);
+ editBookmarkDialog.show(fragmentManager, resources.getString(R.string.edit_bookmark));
}
// Consume the event.
}
});
- // 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);
+ // Create the hamburger icon at the start of the AppBar.
+ actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
// Get a handle for the progress bar.
final ProgressBar progressBar = findViewById(R.id.progress_bar);
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.
- // `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings.
+ // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
+ // `::selection {background: #0D47A1}' sets the text selection highlight color 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)})()", value -> {
+ "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !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.
+ // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
if (progressBar.getVisibility() == View.GONE) {
mainWebView.setVisibility(View.VISIBLE);
}
// Enter full screen video.
@Override
- public void onShowCustomView(View view, CustomViewCallback callback) {
+ public void onShowCustomView(View video, CustomViewCallback callback) {
// Set the full screen video flag.
displayingFullScreenVideo = true;
AdHelper.pauseAd(findViewById(R.id.adview));
}
- // Remove the translucent overlays.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ // Hide the keyboard.
+ inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
+
+ // Hide the main content relative layout.
+ mainContentRelativeLayout.setVisibility(View.GONE);
// Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
- /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
* SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
*/
- rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
-
- // Set `rootCoordinatorLayout` to fill the entire screen.
- rootCoordinatorLayout.setFitsSystemWindows(false);
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
// Disable the sliding drawers.
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
- // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
- fullScreenVideoFrameLayout.addView(view);
+ // Add the video view to the full screen video frame layout.
+ fullScreenVideoFrameLayout.addView(video);
+
+ // Show the full screen video frame layout.
fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
}
// Unset the full screen video flag.
displayingFullScreenVideo = false;
- // Hide `fullScreenVideoFrameLayout`.
+ // Remove all the views from the full screen video frame layout.
fullScreenVideoFrameLayout.removeAllViews();
+
+ // Hide the full screen video frame layout.
fullScreenVideoFrameLayout.setVisibility(View.GONE);
// Enable the sliding drawers.
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+ // Show the main content relative layout.
+ mainContentRelativeLayout.setVisibility(View.VISIBLE);
+
// Apply the appropriate full screen mode the `SYSTEM_UI` flags.
if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
- if (hideSystemBarsOnFullscreen) { // Hide everything.
- // Remove the translucent navigation setting if it is currently flagged.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-
- // Remove the translucent status bar overlay.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
- // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
- drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-
- /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
- rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- } else { // Hide everything except the status and navigation bars.
- // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
- rootCoordinatorLayout.setSystemUiVisibility(0);
-
- // Add the translucent status flag if it is unset.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
- if (translucentNavigationBarOnFullscreen) {
- // Set the navigation bar to be translucent. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- } else {
- // Set the navigation bar to be black.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- }
- }
- } else { // Switch to normal viewing mode.
- // Show the `appBar` if `findOnPageLinearLayout` is not visible.
- if (findOnPageLinearLayout.getVisibility() == View.GONE) {
- appBar.show();
+ // Hide the app bar if specified.
+ if (hideAppBar) {
+ actionBar.hide();
}
- // Show the `BannerAd` in the free flavor.
+ // Hide the banner ad in the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
- AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
+ AdHelper.hideAd(findViewById(R.id.adview));
}
- // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
- rootCoordinatorLayout.setSystemUiVisibility(0);
-
- // Remove the translucent navigation bar flag if it is set.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ } else { // Switch to normal viewing mode.
+ // Remove the `SYSTEM_UI` flags from the root frame layout.
+ rootFrameLayout.setSystemUiVisibility(0);
- // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
+ // Add the translucent status flag.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
- // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
- rootCoordinatorLayout.setFitsSystemWindows(true);
}
- // Show the ad if this is the free flavor.
- if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
+ // Reload the ad for the free flavor if not in full screen mode.
+ if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
+ // Reload the ad.
AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
}
}
DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
// Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
- downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+ downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
} else { // Show the permission request directly.
// Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download file alert dialog.
- AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
+ DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(url, contentDisposition, contentLength);
// Show the download file alert dialog.
- downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
}
}
});
// Hide zoom controls.
mainWebView.getSettings().setDisplayZoomControls(false);
- // Set `mainWebView` to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
+ // Don't allow mixed content (HTTP and HTTPS) on the same website.
+ if (Build.VERSION.SDK_INT >= 21) {
+ mainWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
+ }
+
+ // Set the WebView to use a wide viewport. Otherwise, some web pages will be scrunched and some content will render outside the screen.
mainWebView.getSettings().setUseWideViewPort(true);
- // Set `mainWebView` to load in overview mode (zoomed out to the maximum width).
+ // Set the WebView to load in overview mode (zoomed out to the maximum width).
mainWebView.getSettings().setLoadWithOverviewMode(true);
// Explicitly disable geolocation.
httpAuthHandler = handler;
// Display the HTTP authentication dialog.
- AppCompatDialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
- httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
+ DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm);
+ httpAuthenticationDialogFragment.show(fragmentManager, getString(R.string.http_authentication));
}
// Update the URL in urlTextBox when the page starts to load.
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
+ // This is also used to determine when to check for pinned mismatches.
+ urlIsLoading = true;
+
+ // Reset the list of host IP addresses.
+ currentHostIpAddresses = "";
+
// Reset the list of resource requests.
resourceRequests.clear();
mainWebView.setVisibility(View.INVISIBLE);
}
- // Hide the keyboard. `0` indicates no additional flags.
+ // Hide the keyboard.
inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
// Check to see if Privacy Browser is waiting on Orbot.
- if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
- // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
+ if (!waitingForOrbot) { // Process the URL.
+ // The formatted URL string must be updated at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
formattedUrlString = url;
// Display the formatted URL text.
// Apply text highlighting to `urlTextBox`.
highlightUrlText();
+ // Get a URI for the current URL.
+ Uri currentUri = Uri.parse(formattedUrlString);
+
+ // Get the IP addresses for the host.
+ new GetHostIpAddresses(activity).execute(currentUri.getHost());
+
// Apply any custom domain settings if the URL was loaded by navigating history.
if (navigatingHistory) {
// Apply the domain settings.
}
}
- // Set `urlIsLoading` to `true`, so that redirects while loading do not trigger changes in the user agent, which forces another reload of the existing page.
- urlIsLoading = true;
-
// Replace Refresh with Stop if the menu item has been created. (The WebView typically begins loading before the menu items are instantiated.)
if (refreshMenuItem != null) {
// Set the title.
}
}
- // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
- urlIsLoading = false;
+
// Clear the cache and history if Incognito Mode is enabled.
if (incognitoModeEnabled) {
}
}
- // Update `urlTextBox` and apply domain settings if not waiting on Orbot.
+ // Update the URL text box and apply domain settings if not waiting on Orbot.
if (!waitingForOrbot) {
// Check to see if `WebView` has set `url` to be `about:blank`.
if (url.equals("about:blank")) { // `WebView` is blank, so `formattedUrlString` should be `""` and `urlTextBox` should display a hint.
// Apply the domain settings. This clears any settings from the previous domain.
applyDomainSettings(formattedUrlString, true, false);
} else { // `WebView` has loaded a webpage.
- // Set `formattedUrlString`.
- formattedUrlString = url;
+ // Set the formatted URL string. Getting the URL from the WebView instead of using the one provided by `onPageFinished` makes websites like YouTube function correctly.
+ formattedUrlString = mainWebView.getUrl();
- // Only update `urlTextBox` if the user is not typing in it.
+ // Only update the URL text box if the user is not typing in it.
if (!urlTextBox.hasFocus()) {
// Display the formatted URL text.
urlTextBox.setText(formattedUrlString);
}
}
- // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedSslCertificateMismatchDialog`.
+ // Store the SSL certificate so it can be accessed from `ViewSslCertificateDialog` and `PinnedMismatchDialog`.
sslCertificate = mainWebView.getCertificate();
- // Check the current website SSL certificate against the pinned SSL certificate if there is a pinned SSL certificate the user has not chosen to ignore it for this session.
- // Also ignore if changes in the user agent causes an error while navigating history.
- if (pinnedDomainSslCertificate && !ignorePinnedSslCertificate && navigatingHistory) {
- // Initialize the current SSL certificate variables.
- String currentWebsiteIssuedToCName = "";
- String currentWebsiteIssuedToOName = "";
- String currentWebsiteIssuedToUName = "";
- String currentWebsiteIssuedByCName = "";
- String currentWebsiteIssuedByOName = "";
- String currentWebsiteIssuedByUName = "";
- Date currentWebsiteSslStartDate = null;
- Date currentWebsiteSslEndDate = null;
-
-
- // Extract the individual pieces of information from the current website SSL certificate if it is not null.
- if (sslCertificate != null) {
- currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
- currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
- currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
- currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
- currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
- currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
- currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
- currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
- }
-
- // Initialize `String` variables to store the SSL certificate dates. `Strings` are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
- String currentWebsiteSslStartDateString = "";
- String currentWebsiteSslEndDateString = "";
- String pinnedDomainSslStartDateString = "";
- String pinnedDomainSslEndDateString = "";
-
- // Convert the `Dates` to `Strings` if they are not `null`.
- if (currentWebsiteSslStartDate != null) {
- currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
- }
-
- if (currentWebsiteSslEndDate != null) {
- currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
- }
-
- if (pinnedDomainSslStartDate != null) {
- pinnedDomainSslStartDateString = pinnedDomainSslStartDate.toString();
- }
-
- if (pinnedDomainSslEndDate != null) {
- pinnedDomainSslEndDateString = pinnedDomainSslEndDate.toString();
- }
-
- // 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.
- //Display the pinned SSL certificate mismatch `AlertDialog`.
- AppCompatDialogFragment pinnedSslCertificateMismatchDialogFragment = new PinnedSslCertificateMismatchDialog();
- pinnedSslCertificateMismatchDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_mismatch));
- }
+ // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
+ if (!gettingIpAddresses) {
+ checkPinnedMismatch();
}
}
+
+ // Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes. It is also used to determine when to check for pinned mismatches.
+ urlIsLoading = false;
}
// Handle SSL Certificate errors.
Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
// 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.
- // Proceed to the website without displaying an error.
+ if (pinnedSslCertificate &&
+ currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) && currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) &&
+ currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) && currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) &&
+ currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) && currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) &&
+ currentWebsiteSslStartDate.equals(pinnedSslStartDate) && currentWebsiteSslEndDate.equals(pinnedSslEndDate)) {
+
+ // 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.
// Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
sslErrorHandler = handler;
// Display the SSL error `AlertDialog`.
- AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
- sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
+ DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error);
+ sslCertificateErrorDialogFragment.show(fragmentManager, getString(R.string.ssl_certificate_error));
}
}
});
// Load the URL.
loadUrl(formattedUrlString);
+ // Get a handle for the drawer layout.
+ DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+
// Close the navigation drawer if it is open.
if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
// Update the bookmarks drawer if returning from the Bookmarks activity.
if (restartFromBookmarksActivity) {
+ // Get a handle for the drawer layout.
+ DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+
// Close the bookmarks drawer.
drawerLayout.closeDrawer(GravityCompat.END);
// Resume `mainWebView`.
mainWebView.onResume();
- // Resume the adView for the free flavor.
- if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Resume the ad.
- AdHelper.resumeAd(findViewById(R.id.adview));
- }
-
// Display a message to the user if waiting for Orbot.
if (waitingForOrbot && !orbotStatus.equals("ON")) {
// Disable the wide view port so that the waiting for Orbot text is displayed correctly.
mainWebView.loadData(waitingForOrbotHtmlString, "text/html", null);
}
- if (displayingFullScreenVideo) {
- // Remove the translucent overlays.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
+ // Get a handle for the root frame layouts.
+ FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
- // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
- drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
* SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
*/
- rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ } else if (BuildConfig.FLAVOR.contentEquals("free")) { // Resume the adView for the free flavor.
+ // Resume the ad.
+ AdHelper.resumeAd(findViewById(R.id.adview));
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
+ // Inflate the menu. This adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.webview_options_menu, menu);
// Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
+ // Get a handle for the swipe refresh layout.
+ SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
// 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);
// removeAllCookies is deprecated, but it is required for API < 21.
@SuppressWarnings("deprecation")
public boolean onOptionsItemSelected(MenuItem menuItem) {
+ // Reenter full screen browsing mode if it was interrupted by the options menu. <https://redmine.stoutner.com/issues/389>
+ if (inFullScreenBrowsingMode) {
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+ FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+
// Get the selected menu item ID.
int menuItemId = menuItem.getItemId();
- // Set the commands that relate to the menu entries.
+ // Run the commands that correlate to the selected menu item.
switch (menuItemId) {
case R.id.toggle_javascript:
// Switch the status of javaScriptEnabled.
// Do nothing because everything will be handled by `onDismissed()` below.
})
.addCallback(new Snackbar.Callback() {
+ @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
@Override
public void onDismissed(Snackbar snackbar, int event) {
switch (event) {
- // The user pushed the `Undo` button.
+ // The user pushed the undo button.
case Snackbar.Callback.DISMISS_EVENT_ACTION:
// Do nothing.
break;
- // The `Snackbar` was dismissed without the `Undo` button being pushed.
+ // The snackbar was dismissed without the undo button being pushed.
default:
// `cookieManager.removeAllCookie()` varies by SDK.
if (Build.VERSION.SDK_INT < 21) {
cookieManager.removeAllCookie();
} else {
- // `null` indicates no callback.
cookieManager.removeAllCookies(null);
}
}
// Do nothing because everything will be handled by `onDismissed()` below.
})
.addCallback(new Snackbar.Callback() {
+ @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
@Override
public void onDismissed(Snackbar snackbar, int event) {
switch (event) {
- // The user pushed the `Undo` button.
+ // The user pushed the undo button.
case Snackbar.Callback.DISMISS_EVENT_ACTION:
// Do nothing.
break;
- // The `Snackbar` was dismissed without the `Undo` button being pushed.
+ // The snackbar was dismissed without the undo button being pushed.
default:
// Delete the DOM Storage.
WebStorage webStorage = WebStorage.getInstance();
// Setup a runnable to manually delete the DOM storage files and directories.
Runnable deleteDomStorageRunnable = () -> {
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/"});
+ // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+ Process deleteLocalStorageProcess = 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) {
+ Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+ Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+ Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+ Process deleteDatabasesProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+
+ // Wait for the processes to finish.
+ deleteLocalStorageProcess.waitFor();
+ deleteIndexProcess.waitFor();
+ deleteQuotaManagerProcess.waitFor();
+ deleteQuotaManagerJournalProcess.waitFor();
+ deleteDatabasesProcess.waitFor();
+ } catch (Exception exception) {
// Do nothing if an error is thrown.
}
};
// Do nothing because everything will be handled by `onDismissed()` below.
})
.addCallback(new Snackbar.Callback() {
+ @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
@Override
public void onDismissed(Snackbar snackbar, int event) {
switch (event) {
- // The user pushed the `Undo` button.
+ // The user pushed the undo button.
case Snackbar.Callback.DISMISS_EVENT_ACTION:
// Do nothing.
break;
- // The `Snackbar` was dismissed without the `Undo` button being pushed.
+ // The snackbar was dismissed without the `Undo` button being pushed.
default:
// Delete the form data.
WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
return true;
case R.id.swipe_to_refresh:
+ // Get a handle for the swipe refresh layout.
+ SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
// Toggle swipe to refresh.
swipeRefreshLayout.setEnabled(!swipeRefreshLayout.isEnabled());
return true;
mainWebView.reload();
return true;
- case R.id.print:
- // Get a `PrintManager` instance.
- PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
+ case R.id.find_on_page:
+ // Get a handle for the views.
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
- // Convert `mainWebView` to `printDocumentAdapter`.
- PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
+ // Hide the toolbar.
+ toolbar.setVisibility(View.GONE);
- // Remove the lint error below that `printManager` might be `null`.
- assert printManager != null;
+ // Show the find on page linear layout.
+ findOnPageLinearLayout.setVisibility(View.VISIBLE);
- // Print the document. The print attributes are `null`.
- printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+ // Display the keyboard. The app must 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);
+ }, 200);
return true;
case R.id.view_source:
startActivity(viewSourceIntent);
return true;
- case R.id.proxy_through_orbot:
- // Toggle the proxy through Orbot variable.
- proxyThroughOrbot = !proxyThroughOrbot;
-
- // Apply the proxy through Orbot settings.
- applyProxyThroughOrbot(true);
- return true;
-
- case R.id.share:
+ case R.id.share_url:
// Setup the share string.
String shareString = webViewTitle + " – " + urlTextBox.getText().toString();
shareIntent.setType("text/plain");
// Make it so.
- startActivity(Intent.createChooser(shareIntent, "Share URL"));
+ startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
return true;
- case R.id.find_on_page:
- // Hide the URL app bar.
- supportAppBar.setVisibility(View.GONE);
+ case R.id.print:
+ // Get a `PrintManager` instance.
+ PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
- // Show the Find on Page `RelativeLayout`.
- findOnPageLinearLayout.setVisibility(View.VISIBLE);
+ // Convert `mainWebView` to `printDocumentAdapter`.
+ PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
- // 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();
+ // Remove the lint error below that `printManager` might be `null`.
+ assert printManager != null;
- // Display the keyboard. `0` sets no input flags.
- inputMethodManager.showSoftInput(findOnPageEditText, 0);
- }, 200);
+ // Print the document. The print attributes are `null`.
+ printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
+ return true;
+
+ case R.id.open_with_app:
+ openWithApp(formattedUrlString);
+ return true;
+
+ case R.id.open_with_browser:
+ openWithBrowser(formattedUrlString);
return true;
case R.id.add_to_homescreen:
- // Show the `CreateHomeScreenShortcutDialog` `AlertDialog` and name this instance `R.string.create_shortcut`.
- AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcutDialog();
+ // Instantiate the create home screen shortcut dialog.
+ DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(mainWebView.getTitle(), formattedUrlString, favoriteIconBitmap);
+
+ // Show the create home screen shortcut dialog.
createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
+ return true;
- //Everything else will be handled by `CreateHomeScreenShortcutDialog` and the associated listener below.
+ case R.id.proxy_through_orbot:
+ // Toggle the proxy through Orbot variable.
+ proxyThroughOrbot = !proxyThroughOrbot;
+
+ // Apply the proxy through Orbot settings.
+ applyProxyThroughOrbot(true);
return true;
case R.id.refresh:
case R.id.ad_consent:
// Display the ad consent dialog.
DialogFragment adConsentDialogFragment = new AdConsentDialog();
- adConsentDialogFragment.show(getFragmentManager(), getString(R.string.ad_consent));
+ adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
return true;
default:
// Get the `WebBackForwardList`.
WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
- // Show the `UrlHistoryDialog` `AlertDialog` and name this instance `R.string.history`. `this` is the `Context`.
- AppCompatDialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
+ // Show the URL history dialog and name this instance `R.string.history`.
+ DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(this, webBackForwardList);
urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
break;
startActivity(importExportIntent);
break;
+ case R.id.logcat:
+ // Launch the logcat activity.
+ Intent logcatIntent = new Intent(this, LogcatActivity.class);
+ startActivity(logcatIntent);
+ break;
+
case R.id.guide:
// Launch `GuideActivity`.
Intent guideIntent = new Intent(this, GuideActivity.class);
startActivity(aboutIntent);
break;
- case R.id.clearAndExit:
+ case R.id.clear_and_exit:
// Close the bookmarks cursor and database.
bookmarksCursor.close();
bookmarksDatabaseHelper.close();
- // Get a handle for `sharedPreferences`. `this` references the current context.
+ // Get a handle for the shared preferences.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ // Get the status of the clear everything preference.
boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
// Clear cookies.
// Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
try {
- // We have to use two commands because `Runtime.exec()` does not like `*`.
- privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
- privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
- } catch (IOException e) {
+ // Two commands must be used because `Runtime.exec()` does not like `*`.
+ Process deleteCookiesProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
+ Process deleteCookiesJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
+
+ // Wait until the processes have finished.
+ deleteCookiesProcess.waitFor();
+ deleteCookiesJournalProcess.waitFor();
+ } catch (Exception exception) {
// Do nothing if an error is thrown.
}
}
// 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 {
// 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/"});
+ Process deleteLocalStorageProcess = 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) {
+ Process deleteIndexProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
+ Process deleteQuotaManagerProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
+ Process deleteQuotaManagerJournalProcess = privacyBrowserRuntime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
+ Process deleteDatabaseProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
+
+ // Wait until the processes have finished.
+ deleteLocalStorageProcess.waitFor();
+ deleteIndexProcess.waitFor();
+ deleteQuotaManagerProcess.waitFor();
+ deleteQuotaManagerJournalProcess.waitFor();
+ deleteDatabaseProcess.waitFor();
+ } catch (Exception exception) {
// Do nothing if an error is thrown.
}
}
// Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
try {
- // We have to use a `String[]` because the database contains a space and `Runtime.exec` will not escape the string correctly otherwise.
- privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
- privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
- } catch (IOException e) {
+ // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
+ Process deleteWebDataProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
+ Process deleteWebDataJournalProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
+
+ // Wait until the processes have finished.
+ deleteWebDataProcess.waitFor();
+ deleteWebDataJournalProcess.waitFor();
+ } catch (Exception exception) {
// Do nothing if an error is thrown.
}
}
// Manually delete the cache directories.
try {
// Delete the main cache directory.
- privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
+ Process deleteCacheProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/cache");
// Delete the secondary `Service Worker` cache directory.
- // A `String[]` must be used 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) {
+ // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
+ Process deleteServiceWorkerProcess = privacyBrowserRuntime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
+
+ // Wait until the processes have finished.
+ deleteCacheProcess.waitFor();
+ deleteServiceWorkerProcess.waitFor();
+ } catch (Exception exception) {
// Do nothing if an error is thrown.
}
}
// Clear `customHeaders`.
customHeaders.clear();
- // Detach all views from `mainWebViewRelativeLayout`.
- mainWebViewRelativeLayout.removeAllViews();
-
// Destroy the internal state of `mainWebView`.
mainWebView.destroy();
// See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
if (clearEverything) {
try {
- privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
- } catch (IOException e) {
+ // Delete the folder.
+ Process deleteAppWebviewProcess = privacyBrowserRuntime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
+
+ // Wait until the process has finished.
+ deleteAppWebviewProcess.waitFor();
+ } catch (Exception exception) {
// Do nothing if an error is thrown.
}
}
break;
}
+ // Get a handle for the drawer layout.
+ DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+
// Close the navigation drawer.
drawerLayout.closeDrawer(GravityCompat.START);
return true;
@Override
public void onPostCreate(Bundle savedInstanceState) {
+ // Run the default commands.
super.onPostCreate(savedInstanceState);
- // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
- drawerToggle.syncState();
+ // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
+ actionBarDrawerToggle.syncState();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ // Run the default commands.
super.onConfigurationChanged(newConfig);
// Get the status bar pixel size.
final String imageUrl;
final String linkUrl;
- // Get a handle for the `ClipboardManager`.
+ // Get a handle for the the clipboard and fragment managers.
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ FragmentManager fragmentManager = getSupportFragmentManager();
// Remove the lint errors below that `clipboardManager` might be `null`.
assert clipboardManager != null;
DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
// Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
- downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+ downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
} else { // Show the permission request directly.
// Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download file alert dialog.
- AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
+ DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
// Show the download file alert dialog.
- downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
}
}
return false;
});
- // Add a `Cancel` entry, which by default closes the `ContextMenu`.
+ // Add an Open with App entry.
+ menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+ openWithApp(linkUrl);
+ return false;
+ });
+
+ // Add an Open with Browser entry.
+ menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+ openWithBrowser(linkUrl);
+ return false;
+ });
+
+ // Add a Cancel entry, which by default closes the context menu.
menu.add(R.string.cancel);
break;
// Set the target URL as the title of the `ContextMenu`.
menu.setHeaderTitle(linkUrl);
- // Add a `Write Email` entry.
+ // Add a Write Email entry.
menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
// Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
return false;
});
- // Add a `Copy Email Address` entry.
+ // Add a Copy Email Address entry.
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 image URL as the title of the `ContextMenu`.
menu.setHeaderTitle(imageUrl);
- // Add a `View Image` entry.
+ // Add a View Image entry.
menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
loadUrl(imageUrl);
return false;
});
- // Add a `Download Image` entry.
+ // Add a Download Image entry.
menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
// Check if the download should be processed by an external app.
if (downloadWithExternalApp) { // Download with an external app.
DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
// Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
- downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+ downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
} else { // Show the permission request directly.
// Request the permission. The download dialog will be launched by `onRequestPermissionResult().
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download image alert dialog.
- AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+ DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
// Show the download image alert dialog.
- downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
}
}
return false;
});
- // Add a `Copy URL` entry.
+ // Add a Copy URL entry.
menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
// Save the image URL in a `ClipData`.
ClipData srcImageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
return false;
});
+ // Add an Open with App entry.
+ menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+ openWithApp(imageUrl);
+ return false;
+ });
+
+ // Add an Open with Browser entry.
+ menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+ openWithBrowser(imageUrl);
+ return false;
+ });
+
// Add a `Cancel` entry, which by default closes the `ContextMenu`.
menu.add(R.string.cancel);
break;
DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
// Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
- downloadLocationPermissionDialogFragment.show(getFragmentManager(), getString(R.string.download_location));
+ downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
} else { // Show the permission request directly.
// Request the permission. The download dialog will be launched by `onRequestPermissionResult().
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
}
} else { // The storage permission has already been granted.
// Get a handle for the download image alert dialog.
- AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
+ DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
// Show the download image alert dialog.
- downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
}
}
return false;
return false;
});
+ // Add an Open with App entry.
+ menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
+ openWithApp(imageUrl);
+ return false;
+ });
+
+ // Add an Open with Browser entry.
+ menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
+ openWithBrowser(imageUrl);
+ return false;
+ });
+
// Add a `Cancel` entry, which by default closes the `ContextMenu`.
menu.add(R.string.cancel);
break;
}
@Override
- public void onCreateBookmark(AppCompatDialogFragment dialogFragment) {
- // Get the `EditTexts` from the `dialogFragment`.
+ public void onCreateBookmark(DialogFragment dialogFragment) {
+ // Get the views from the dialog fragment.
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`.
+ // Extract the strings from the edit texts.
String bookmarkNameString = createBookmarkNameEditText.getText().toString();
String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
- // Convert the favoriteIcon Bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
+ // Get a copy of the favorite icon bitmap.
+ Bitmap favoriteIcon = favoriteIconBitmap;
+
+ // Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
+ if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) {
+ favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true);
+ }
+
+ // Create a favorite icon byte array output stream.
ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
- favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+
+ // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
+ favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
+
+ // Convert the favorite icon byte array stream to a byte array.
byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
// Display the new bookmark below the current items in the (0 indexed) list.
// Create the bookmark.
bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
- // Update `bookmarksCursor` with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
- // Update the `ListView`.
+ // Update the list view.
bookmarksCursorAdapter.changeCursor(bookmarksCursor);
// Scroll to the new bookmark.
}
@Override
- public void onCreateBookmarkFolder(AppCompatDialogFragment dialogFragment) {
- // Get handles for the views in `dialogFragment`.
+ public void onCreateBookmarkFolder(DialogFragment dialogFragment) {
+ // Get handles for the views in the dialog fragment.
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();
- // Get the new folder icon `Bitmap`.
+ // Create a folder icon bitmap.
Bitmap folderIconBitmap;
+
+ // Set the folder icon bitmap according to the dialog.
if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
- // Get the default folder icon and convert it to a `Bitmap`.
+ // Get the default folder icon drawable.
Drawable folderIconDrawable = folderIconImageView.getDrawable();
+
+ // Convert the folder icon drawable to a bitmap drawable.
BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+
+ // Convert the folder icon bitmap drawable to a bitmap.
folderIconBitmap = folderIconBitmapDrawable.getBitmap();
- } else { // Use the `WebView` favorite icon.
+ } else { // Use the WebView favorite icon.
+ // Get a copy of the favorite icon bitmap.
folderIconBitmap = favoriteIconBitmap;
+
+ // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
+ if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
+ folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
+ }
}
- // Convert `folderIconBitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
+ // Create a folder icon byte array output stream.
ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
+
+ // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
+
+ // Convert the folder icon byte array stream to a byte array.
byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
// Move all the bookmarks down one in the display order.
// 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);
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
// Update the `ListView`.
bookmarksCursorAdapter.changeCursor(bookmarksCursor);
}
@Override
- public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
- // Get the shortcut name.
- EditText shortcutNameEditText = dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
- String shortcutNameString = shortcutNameEditText.getText().toString();
+ public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
+ // Get handles for the views from `dialogFragment`.
+ 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();
+ String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
+
+ // Update the bookmark.
+ if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
+ bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
+ } else { // Update the bookmark using the `WebView` favorite icon.
+ // Get a copy of the favorite icon bitmap.
+ Bitmap favoriteIcon = favoriteIconBitmap;
+
+ // Scale the favorite icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
+ if ((favoriteIcon.getHeight() > 256) || (favoriteIcon.getWidth() > 256)) {
+ favoriteIcon = Bitmap.createScaledBitmap(favoriteIcon, 256, 256, true);
+ }
+
+ // Create a favorite icon byte array output stream.
+ ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
+
+ // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
+ favoriteIcon.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
+
+ // Convert the favorite icon byte array stream to a byte array.
+ byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
+
+ // Update the bookmark and the favorite icon.
+ bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
+ }
+
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
+
+ // Update the list view.
+ bookmarksCursorAdapter.changeCursor(bookmarksCursor);
+ }
+
+ @Override
+ public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId) {
+ // Get handles for the views from `dialogFragment`.
+ 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 defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
+
+ // Get the new folder name.
+ String newFolderNameString = editFolderNameEditText.getText().toString();
+
+ // Check if the favorite icon has changed.
+ if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
+ // Update the name in the database.
+ bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
+ } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
+ // Create the new folder icon Bitmap.
+ Bitmap folderIconBitmap;
+
+ // Populate the new folder icon bitmap.
+ if (defaultFolderIconRadioButton.isChecked()) {
+ // Get the default folder icon drawable.
+ Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
+
+ // Convert the folder icon drawable to a bitmap drawable.
+ BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+
+ // Convert the folder icon bitmap drawable to a bitmap.
+ folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+ } else { // Use the `WebView` favorite icon.
+ // Get a copy of the favorite icon bitmap.
+ folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
+
+ // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
+ if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
+ folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
+ }
+ }
+
+ // Create a folder icon byte array output stream.
+ ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
+
+ // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
+ folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
+
+ // Convert the folder icon byte array stream to a byte array.
+ byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
+
+ // Update the folder icon in the database.
+ bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
+ } else { // The folder icon and the name have changed.
+ // Get the new folder icon `Bitmap`.
+ Bitmap folderIconBitmap;
+ if (defaultFolderIconRadioButton.isChecked()) {
+ // Get the default folder icon drawable.
+ Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
+
+ // Convert the folder icon drawable to a bitmap drawable.
+ BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
+
+ // Convert the folder icon bitmap drawable to a bitmap.
+ folderIconBitmap = folderIconBitmapDrawable.getBitmap();
+ } else { // Use the `WebView` favorite icon.
+ // Get a copy of the favorite icon bitmap.
+ folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
+
+ // Scale the folder icon bitmap down if it is larger than 256 x 256. Filtering uses bilinear interpolation.
+ if ((folderIconBitmap.getHeight() > 256) || (folderIconBitmap.getWidth() > 256)) {
+ folderIconBitmap = Bitmap.createScaledBitmap(folderIconBitmap, 256, 256, true);
+ }
+ }
+
+ // Create a folder icon byte array output stream.
+ ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
- // Convert the favorite icon bitmap to an `Icon`. `IconCompat` is required until API >= 26.
- IconCompat favoriteIcon = IconCompat.createWithBitmap(favoriteIconBitmap);
+ // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
+ folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
- // Setup the shortcut intent.
- Intent shortcutIntent = new Intent(Intent.ACTION_VIEW);
- shortcutIntent.setData(Uri.parse(formattedUrlString));
+ // Convert the folder icon byte array stream to a byte array.
+ byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
- // Create a shortcut info builder. The shortcut name becomes the shortcut ID.
- ShortcutInfoCompat.Builder shortcutInfoBuilder = new ShortcutInfoCompat.Builder(this, shortcutNameString);
+ // Update the folder name and icon in the database.
+ bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
+ }
- // Add the required fields to the shortcut info builder.
- shortcutInfoBuilder.setIcon(favoriteIcon);
- shortcutInfoBuilder.setIntent(shortcutIntent);
- shortcutInfoBuilder.setShortLabel(shortcutNameString);
+ // Update the bookmarks cursor with the current contents of this folder.
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
- // Request the pin. `ShortcutManagerCompat` can be switched to `ShortcutManager` once API >= 26.
- ShortcutManagerCompat.requestPinShortcut(this, shortcutInfoBuilder.build(), null);
+ // Update the `ListView`.
+ bookmarksCursorAdapter.changeCursor(bookmarksCursor);
}
@Override
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ // Get a handle for the fragment manager.
+ FragmentManager fragmentManager = getSupportFragmentManager();
+
switch (requestCode) {
case DOWNLOAD_FILE_REQUEST_CODE:
// Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
- AppCompatDialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
+ DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
// On API 23, displaying the fragment must be delayed or the app will crash.
if (Build.VERSION.SDK_INT == 23) {
- new Handler().postDelayed(() -> downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
+ new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
} else {
- downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
}
// Reset the download variables.
case DOWNLOAD_IMAGE_REQUEST_CODE:
// Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
- AppCompatDialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
+ DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
// On API 23, displaying the fragment must be delayed or the app will crash.
if (Build.VERSION.SDK_INT == 23) {
- new Handler().postDelayed(() -> downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download)), 500);
+ new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
} else {
- downloadImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
+ downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
}
// Reset the image URL variable.
}
@Override
- public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
+ public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
// Download the image if it has an HTTP or HTTPS URI.
if (imageUrl.startsWith("http")) {
// Get a handle for the system `DOWNLOAD_SERVICE`.
}
@Override
- public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
+ public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
// Download the file if it has an HTTP or HTTPS URI.
if (downloadUrl.startsWith("http")) {
// Get a handle for the system `DOWNLOAD_SERVICE`.
}
}
- @Override
- public void onSaveBookmark(AppCompatDialogFragment dialogFragment, int selectedBookmarkDatabaseId) {
- // Get handles for the views from `dialogFragment`.
- 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();
- String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
-
- // Update the bookmark.
- if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
- bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
- } else { // Update the bookmark using the `WebView` favorite icon.
- // Convert the favorite icon to a byte array. `0` is for lossless compression (the only option for a PNG).
- ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
- favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
- byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
-
- // Update the bookmark and the favorite icon.
- bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
- }
-
- // Update `bookmarksCursor` with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
-
- // Update the `ListView`.
- bookmarksCursorAdapter.changeCursor(bookmarksCursor);
- }
-
- @Override
- public void onSaveBookmarkFolder(AppCompatDialogFragment dialogFragment, int selectedFolderDatabaseId) {
- // Get handles for the views from `dialogFragment`.
- 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();
-
- // Check if the favorite icon has changed.
- if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
- // Update the name in the database.
- bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
- } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
- // Get the new folder icon `Bitmap`.
- Bitmap folderIconBitmap;
- if (defaultFolderIconRadioButton.isChecked()) {
- // Get the default folder icon and convert it to a `Bitmap`.
- Drawable folderIconDrawable = folderIconImageView.getDrawable();
- BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
- folderIconBitmap = folderIconBitmapDrawable.getBitmap();
- } else { // Use the `WebView` favorite icon.
- folderIconBitmap = favoriteIconBitmap;
- }
-
- // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
- ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
- folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
- byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
-
- // Update the folder icon in the database.
- bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, folderIconByteArray);
- } else { // The folder icon and the name have changed.
- // Get the new folder icon `Bitmap`.
- Bitmap folderIconBitmap;
- if (defaultFolderIconRadioButton.isChecked()) {
- // Get the default folder icon and convert it to a `Bitmap`.
- Drawable folderIconDrawable = folderIconImageView.getDrawable();
- BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
- folderIconBitmap = folderIconBitmapDrawable.getBitmap();
- } else { // Use the `WebView` favorite icon.
- folderIconBitmap = MainWebViewActivity.favoriteIconBitmap;
- }
-
- // Convert the folder `Bitmap` to a byte array. `0` is for lossless compression (the only option for a PNG).
- ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
- folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
- byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
-
- // Update the folder name and icon in the database.
- bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, folderIconByteArray);
- }
-
- // Update `bookmarksCursor` with the current contents of this folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
-
- // Update the `ListView`.
- bookmarksCursorAdapter.changeCursor(bookmarksCursor);
- }
-
@Override
public void onHttpAuthenticationCancel() {
// Cancel the `HttpAuthHandler`.
}
@Override
- public void onHttpAuthenticationProceed(AppCompatDialogFragment dialogFragment) {
+ public void onHttpAuthenticationProceed(DialogFragment dialogFragment) {
// Get handles for the `EditTexts`.
EditText usernameEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_username);
EditText passwordEditText = dialogFragment.getDialog().findViewById(R.id.http_authentication_password);
public void viewSslCertificate(View view) {
// Show the `ViewSslCertificateDialog` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificateDialog();
- viewSslCertificateDialogFragment.show(getFragmentManager(), getString(R.string.view_ssl_certificate));
+ viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
}
@Override
}
@Override
- public void onSslMismatchBack() {
+ public void onPinnedMismatchBack() {
if (mainWebView.canGoBack()) { // There is a back page in the history.
// Reset the formatted URL string so the page will load correctly if blocking of third-party requests is enabled.
formattedUrlString = "";
}
@Override
- public void onSslMismatchProceed() {
- // Do not check the pinned SSL certificate for this domain again until the domain changes.
- ignorePinnedSslCertificate = true;
+ public void onPinnedMismatchProceed() {
+ // Do not check the pinned information for this domain again until the domain changes.
+ ignorePinnedDomainInformation = true;
}
@Override
// Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
@Override
public void onBackPressed() {
+ // Get a handle for the drawer layout.
+ DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
+
if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
// Close the navigation drawer.
drawerLayout.closeDrawer(GravityCompat.START);
drawerLayout.closeDrawer(GravityCompat.END);
} else { // A subfolder is displayed.
// Place the former parent folder in `currentFolder`.
- currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolder(currentBookmarksFolder);
+ currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
// Load the new folder.
loadBookmarksFolder();
}
public void closeFindOnPage(View view) {
+ // Get a handle for the views.
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
+
// Delete the contents of `find_on_page_edittext`.
findOnPageEditText.setText(null);
// Clear the highlighted phrases.
mainWebView.clearMatches();
- // Hide the Find on Page `RelativeLayout`.
+ // Hide the find on page linear layout.
findOnPageLinearLayout.setVisibility(View.GONE);
- // Show the URL app bar.
- supportAppBar.setVisibility(View.VISIBLE);
+ // Show the toolbar.
+ toolbar.setVisibility(View.VISIBLE);
- // Hide the keyboard so we can see the webpage. `0` indicates no additional flags.
+ // Hide the keyboard.
inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
}
boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
- hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
- translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
+ hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
downloadWithExternalApp = sharedPreferences.getBoolean("download_with_external_app", false);
+ // Get handles for the views that need to be modified. `getSupportActionBar()` must be used until the minimum API >= 21.
+ FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
+ ActionBar actionBar = getSupportActionBar();
+
+ // Remove the incorrect lint warnings below that the action bar might be null.
+ assert actionBar != null;
+
// Apply the proxy through Orbot settings.
applyProxyThroughOrbot(false);
customHeaders.remove("DNT");
}
- // Apply the appropriate full screen mode the `SYSTEM_UI` flags.
- if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
- if (hideSystemBarsOnFullscreen) { // Hide everything.
- // Remove the translucent navigation setting if it is currently flagged.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-
- // Remove the translucent status bar overlay.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ // Set the app bar scrolling.
+ mainWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
- // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
- drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ // Update the full screen browsing mode settings.
+ if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
+ // Update the visibility of the app bar, which might have changed in the settings.
+ if (hideAppBar) {
+ actionBar.hide();
+ } else {
+ actionBar.show();
+ }
- /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
- * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
- * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
- */
- rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
- } else { // Hide everything except the status and navigation bars.
- // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
- rootCoordinatorLayout.setSystemUiVisibility(0);
+ // Hide the banner ad in the free flavor.
+ if (BuildConfig.FLAVOR.contentEquals("free")) {
+ AdHelper.hideAd(findViewById(R.id.adview));
+ }
- // Add the translucent status flag if it is unset.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+ // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
- if (translucentNavigationBarOnFullscreen) {
- // Set the navigation bar to be translucent.
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- } else {
- // Set the navigation bar to be black.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
- }
- }
+ /* Hide the system bars.
+ * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
+ * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
+ * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
+ * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
+ */
+ rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
} else { // Privacy Browser is not in full screen browsing mode.
// Reset the full screen tracker, which could be true if Privacy Browser was in full screen mode before entering settings and full screen browsing was disabled.
inFullScreenBrowsingMode = false;
- // Show the `appBar` if `findOnPageLinearLayout` is not visible.
- if (findOnPageLinearLayout.getVisibility() == View.GONE) {
- appBar.show();
- }
+ // Show the app bar.
+ actionBar.show();
- // Show the `BannerAd` in the free flavor.
+ // Show the banner ad in the free flavor.
if (BuildConfig.FLAVOR.contentEquals("free")) {
- // Initialize the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
- AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
+ // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead.
+ AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), fragmentManager, getString(R.string.google_app_id), getString(R.string.ad_unit_id));
}
- // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
- rootCoordinatorLayout.setSystemUiVisibility(0);
+ // Remove the `SYSTEM_UI` flags from the root frame layout.
+ rootFrameLayout.setSystemUiVisibility(0);
- // Remove the translucent navigation bar flag if it is set.
- getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
-
- // Add the translucent status flag if it is unset. This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
+ // Add the translucent status flag.
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
-
- // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
- rootCoordinatorLayout.setFitsSystemWindows(true);
}
}
+
// `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
// The deprecated `.getDrawable()` must be used until the minimum API >= 21.
@SuppressWarnings("deprecation")
// Set the new `hostname` as the `currentDomainName`.
currentDomainName = hostName;
- // Reset `ignorePinnedSslCertificate`.
- ignorePinnedSslCertificate = false;
+ // Reset the ignoring of pinned domain information.
+ ignorePinnedDomainInformation = false;
// Reset the favorite icon if specified.
if (resetFavoriteIcon) {
favoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(favoriteIconBitmap, 64, 64, true));
}
+ // Get a handle for the swipe refresh layout.
+ SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
+
// Initialize the database handler. 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);
int swipeToRefreshInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
int displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
- pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
- pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
- pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
- pinnedDomainSslIssuedToUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
- pinnedDomainSslIssuedByCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
- pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
- pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+ pinnedSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
+ pinnedSslIssuedToCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
+ pinnedSslIssuedToOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
+ pinnedSslIssuedToUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
+ pinnedSslIssuedByCName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
+ pinnedSslIssuedByOName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
+ pinnedSslIssuedByUName = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+ pinnedIpAddresses = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
+ pinnedHostIpAddresses = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
// Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
switch (nightModeInt) {
// Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
- pinnedDomainSslStartDate = null;
+ pinnedSslStartDate = null;
} else {
- pinnedDomainSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
+ pinnedSslStartDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
}
// Set the pinned SSL certificate end date to `null` if the saved date `long` is 0.
if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
- pinnedDomainSslEndDate = null;
+ pinnedSslEndDate = null;
} else {
- pinnedDomainSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
+ pinnedSslEndDate = new Date(currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
}
// Close `currentHostDomainSettingsCursor`.
mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
}
- // Reset the pinned SSL certificate information.
+ // Reset the pinned variables.
domainSettingsDatabaseId = -1;
- pinnedDomainSslCertificate = false;
- pinnedDomainSslIssuedToCNameString = "";
- pinnedDomainSslIssuedToONameString = "";
- pinnedDomainSslIssuedToUNameString = "";
- pinnedDomainSslIssuedByCNameString = "";
- pinnedDomainSslIssuedByONameString = "";
- pinnedDomainSslIssuedByUNameString = "";
- pinnedDomainSslStartDate = null;
- pinnedDomainSslEndDate = null;
+ pinnedSslCertificate = false;
+ pinnedSslIssuedToCName = "";
+ pinnedSslIssuedToOName = "";
+ pinnedSslIssuedToUName = "";
+ pinnedSslIssuedByCName = "";
+ pinnedSslIssuedByOName = "";
+ pinnedSslIssuedByUName = "";
+ pinnedSslStartDate = null;
+ pinnedSslEndDate = null;
+ pinnedIpAddresses = false;
+ pinnedHostIpAddresses = "";
// Set third-party cookies status if API >= 21.
if (Build.VERSION.SDK_INT >= 21) {
// Set the loading of webpage images.
mainWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
- // Set a transparent background on `urlTextBox`. We have to use the deprecated `.getDrawable()` until the minimum API >= 21.
+ // Set a transparent background on `urlTextBox`. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
urlAppBarRelativeLayout.setBackgroundDrawable(getResources().getDrawable(R.color.transparent));
}
String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
+ // Get a handle for the action bar. `getSupportActionBar()` must be used until the minimum API >= 21.
+ ActionBar actionBar = getSupportActionBar();
+
+ // Remove the incorrect lint warning later that the action bar might be null.
+ assert actionBar != null;
+
// Set the homepage, search, and proxy options.
if (proxyThroughOrbot) { // Set the Tor options.
// Set `torHomepageString` as `homepage`.
// Set the `appBar` background to indicate proxying through Orbot is enabled. `this` refers to the context.
if (darkTheme) {
- appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
+ actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.dark_blue_30));
} else {
- appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
+ actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.blue_50));
}
// Check to see if Orbot is ready.
// Set the default `appBar` background. `this` refers to the context.
if (darkTheme) {
- appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
+ actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_900));
} else {
- appBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
+ actionBar.setBackgroundDrawable(ContextCompat.getDrawable(this, R.color.gray_100));
}
// Reset `waitingForOrbot.
// Create a download intent. Not specifying the action type will display the maximum number of options.
Intent downloadIntent = new Intent();
- // Set the URI and the mime type. `"*/*"` will display the maximum number of options.
+ // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
downloadIntent.setDataAndType(Uri.parse(url), "text/html");
// Flag the intent to open in a new task.
}
private void highlightUrlText() {
- // Get the URL string.
- String urlString = urlTextBox.getText().toString();
-
- // Highlight the URL according to the protocol.
- if (urlString.startsWith("file://")) { // This is a file URL.
- // De-emphasize only the protocol.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else if (urlString.startsWith("content://")) {
- // De-emphasize only the protocol.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else { // This is a web URL.
- // Get the index of the `/` immediately after the domain name.
- int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
-
- // Create a base URL string.
- String baseUrl;
-
- // Get the base URL.
- if (endOfDomainName > 0) { // There is at least one character after the base URL.
+ // Only highlight the URL text if the box is not currently selected.
+ if (!urlTextBox.hasFocus()) {
+ // Get the URL string.
+ String urlString = urlTextBox.getText().toString();
+
+ // Highlight the URL according to the protocol.
+ if (urlString.startsWith("file://")) { // This is a file URL.
+ // De-emphasize only the protocol.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ } else if (urlString.startsWith("content://")) {
+ // De-emphasize only the protocol.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ } else { // This is a web URL.
+ // Get the index of the `/` immediately after the domain name.
+ int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
+
+ // Create a base URL string.
+ String baseUrl;
+
// Get the base URL.
- baseUrl = urlString.substring(0, endOfDomainName);
- } else { // There are no characters after the base URL.
- // Set the base URL to be the entire URL string.
- baseUrl = urlString;
- }
+ if (endOfDomainName > 0) { // There is at least one character after the base URL.
+ // Get the base URL.
+ baseUrl = urlString.substring(0, endOfDomainName);
+ } else { // There are no characters after the base URL.
+ // Set the base URL to be the entire URL string.
+ baseUrl = urlString;
+ }
- // Get the index of the last `.` in the domain.
- int lastDotIndex = baseUrl.lastIndexOf(".");
+ // Get the index of the last `.` in the domain.
+ int lastDotIndex = baseUrl.lastIndexOf(".");
- // Get the index of the penultimate `.` in the domain.
- int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
+ // Get the index of the penultimate `.` in the domain.
+ int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
- // Markup the beginning of the URL.
- if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
- urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // Markup the beginning of the URL.
+ if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
+ urlTextBox.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- // De-emphasize subdomains.
- if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- }
- } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
- if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
- // De-emphasize the protocol and the additional subdomains.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- } else { // There is only one subdomain in the domain name.
- // De-emphasize only the protocol.
- urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // De-emphasize subdomains.
+ if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
+ } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
+ if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
+ // De-emphasize the protocol and the additional subdomains.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ } else { // There is only one subdomain in the domain name.
+ // De-emphasize only the protocol.
+ urlTextBox.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
}
- }
- // De-emphasize the text after the domain name.
- if (endOfDomainName > 0) {
- urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // De-emphasize the text after the domain name.
+ if (endOfDomainName > 0) {
+ urlTextBox.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ }
}
}
}
private void loadBookmarksFolder() {
// Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
- bookmarksCursor = bookmarksDatabaseHelper.getAllBookmarksCursorByDisplayOrder(currentBookmarksFolder);
+ bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
// Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
bookmarksTitleTextView.setText(currentBookmarksFolder);
}
}
+
+ private void openWithApp(String url) {
+ // Create the open with intent with `ACTION_VIEW`.
+ Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
+
+ // Set the URI but not the MIME type. This should open all available apps.
+ openWithAppIntent.setData(Uri.parse(url));
+
+ // Flag the intent to open in a new task.
+ openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Show the chooser.
+ startActivity(openWithAppIntent);
+ }
+
+ private void openWithBrowser(String url) {
+ // Create the open with intent with `ACTION_VIEW`.
+ Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
+
+ // Set the URI and the MIME type. `"text/html"` should load browser options.
+ openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
+
+ // Flag the intent to open in a new task.
+ openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Show the chooser.
+ startActivity(openWithBrowserIntent);
+ }
+
+ private static void checkPinnedMismatch() {
+ if ((pinnedSslCertificate || pinnedIpAddresses) && !ignorePinnedDomainInformation) {
+ // Initialize the current SSL certificate variables.
+ String currentWebsiteIssuedToCName = "";
+ String currentWebsiteIssuedToOName = "";
+ String currentWebsiteIssuedToUName = "";
+ String currentWebsiteIssuedByCName = "";
+ String currentWebsiteIssuedByOName = "";
+ String currentWebsiteIssuedByUName = "";
+ Date currentWebsiteSslStartDate = null;
+ Date currentWebsiteSslEndDate = null;
+
+
+ // Extract the individual pieces of information from the current website SSL certificate if it is not null.
+ if (sslCertificate != null) {
+ currentWebsiteIssuedToCName = sslCertificate.getIssuedTo().getCName();
+ currentWebsiteIssuedToOName = sslCertificate.getIssuedTo().getOName();
+ currentWebsiteIssuedToUName = sslCertificate.getIssuedTo().getUName();
+ currentWebsiteIssuedByCName = sslCertificate.getIssuedBy().getCName();
+ currentWebsiteIssuedByOName = sslCertificate.getIssuedBy().getOName();
+ currentWebsiteIssuedByUName = sslCertificate.getIssuedBy().getUName();
+ currentWebsiteSslStartDate = sslCertificate.getValidNotBeforeDate();
+ currentWebsiteSslEndDate = sslCertificate.getValidNotAfterDate();
+ }
+
+ // Initialize string variables to store the SSL certificate dates. Strings are needed to compare the values below, which doesn't work with `Dates` if they are `null`.
+ String currentWebsiteSslStartDateString = "";
+ String currentWebsiteSslEndDateString = "";
+ String pinnedSslStartDateString = "";
+ String pinnedSslEndDateString = "";
+
+ // Convert the `Dates` to `Strings` if they are not `null`.
+ if (currentWebsiteSslStartDate != null) {
+ currentWebsiteSslStartDateString = currentWebsiteSslStartDate.toString();
+ }
+
+ if (currentWebsiteSslEndDate != null) {
+ currentWebsiteSslEndDateString = currentWebsiteSslEndDate.toString();
+ }
+
+ if (pinnedSslStartDate != null) {
+ pinnedSslStartDateString = pinnedSslStartDate.toString();
+ }
+
+ if (pinnedSslEndDate != null) {
+ pinnedSslEndDateString = pinnedSslEndDate.toString();
+ }
+
+ // Check to see if the pinned information matches the current information.
+ if ((pinnedIpAddresses && !currentHostIpAddresses.equals(pinnedHostIpAddresses)) || (pinnedSslCertificate && (!currentWebsiteIssuedToCName.equals(pinnedSslIssuedToCName) ||
+ !currentWebsiteIssuedToOName.equals(pinnedSslIssuedToOName) || !currentWebsiteIssuedToUName.equals(pinnedSslIssuedToUName) ||
+ !currentWebsiteIssuedByCName.equals(pinnedSslIssuedByCName) || !currentWebsiteIssuedByOName.equals(pinnedSslIssuedByOName) ||
+ !currentWebsiteIssuedByUName.equals(pinnedSslIssuedByUName) || !currentWebsiteSslStartDateString.equals(pinnedSslStartDateString) ||
+ !currentWebsiteSslEndDateString.equals(pinnedSslEndDateString)))) {
+
+ // Get a handle for the pinned mismatch alert dialog.
+ DialogFragment pinnedMismatchDialogFragment = PinnedMismatchDialog.displayDialog(pinnedSslCertificate, pinnedIpAddresses);
+
+ // Show the pinned mismatch alert dialog.
+ pinnedMismatchDialogFragment.show(fragmentManager, "Pinned Mismatch");
+ }
+ }
+ }
+
+ // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `String` contains the results.
+ private static class GetHostIpAddresses extends AsyncTask<String, Void, String> {
+ // The weak references are used to determine if the activity have disappeared while the AsyncTask is running.
+ private final WeakReference<Activity> activityWeakReference;
+
+ GetHostIpAddresses(Activity activity) {
+ // Populate the weak references.
+ activityWeakReference = new WeakReference<>(activity);
+ }
+
+ // `onPreExecute()` operates on the UI thread.
+ @Override
+ protected void onPreExecute() {
+ // Get a handle for the activity.
+ Activity activity = activityWeakReference.get();
+
+ // Abort if the activity is gone.
+ if ((activity == null) || activity.isFinishing()) {
+ return;
+ }
+
+ // Set the getting IP addresses tracker.
+ gettingIpAddresses = true;
+ }
+
+
+ @Override
+ protected String doInBackground(String... domainName) {
+ // Get a handle for the activity.
+ Activity activity = activityWeakReference.get();
+
+ // Abort if the activity is gone.
+ if ((activity == null) || activity.isFinishing()) {
+ // Return an empty spannable string builder.
+ return "";
+ }
+
+ // Initialize an IP address string builder.
+ StringBuilder ipAddresses = new StringBuilder();
+
+ // Get an array with the IP addresses for the host.
+ try {
+ // Get an array with all the IP addresses for the domain.
+ InetAddress[] inetAddressesArray = InetAddress.getAllByName(domainName[0]);
+
+ // Add each IP address to the string builder.
+ for (InetAddress inetAddress : inetAddressesArray) {
+ if (ipAddresses.length() == 0) { // This is the first IP address.
+ // Add the IP address to the string builder.
+ ipAddresses.append(inetAddress.getHostAddress());
+ } else { // This is not the first IP address.
+ // Add a line break to the string builder first.
+ ipAddresses.append("\n");
+
+ // Add the IP address to the string builder.
+ ipAddresses.append(inetAddress.getHostAddress());
+ }
+ }
+ } catch (UnknownHostException exception) {
+ // Do nothing.
+ }
+
+ // Return the string.
+ return ipAddresses.toString();
+ }
+
+ // `onPostExecute()` operates on the UI thread.
+ @Override
+ protected void onPostExecute(String ipAddresses) {
+ // Get a handle for the activity.
+ Activity activity = activityWeakReference.get();
+
+ // Abort if the activity is gone.
+ if ((activity == null) || activity.isFinishing()) {
+ return;
+ }
+
+ // Store the IP addresses.
+ currentHostIpAddresses = ipAddresses;
+
+ if (!urlIsLoading) {
+ checkPinnedMismatch();
+ }
+
+ // Reset the getting IP addresses tracker.
+ gettingIpAddresses = false;
+ }
+ }
}
\ No newline at end of file