2 * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DownloadManager;
28 import android.app.SearchManager;
29 import android.content.ActivityNotFoundException;
30 import android.content.BroadcastReceiver;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.SharedPreferences;
37 import android.content.pm.PackageManager;
38 import android.content.res.Configuration;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.Typeface;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.graphics.drawable.Drawable;
45 import android.net.Uri;
46 import android.net.http.SslCertificate;
47 import android.net.http.SslError;
48 import android.os.Build;
49 import android.os.Bundle;
50 import android.os.Environment;
51 import android.os.Handler;
52 import android.preference.PreferenceManager;
53 import android.print.PrintDocumentAdapter;
54 import android.print.PrintManager;
55 import android.text.Editable;
56 import android.text.Spanned;
57 import android.text.TextWatcher;
58 import android.text.style.ForegroundColorSpan;
59 import android.util.Patterns;
60 import android.view.ContextMenu;
61 import android.view.GestureDetector;
62 import android.view.KeyEvent;
63 import android.view.Menu;
64 import android.view.MenuItem;
65 import android.view.MotionEvent;
66 import android.view.View;
67 import android.view.ViewGroup;
68 import android.view.WindowManager;
69 import android.view.inputmethod.InputMethodManager;
70 import android.webkit.CookieManager;
71 import android.webkit.HttpAuthHandler;
72 import android.webkit.SslErrorHandler;
73 import android.webkit.ValueCallback;
74 import android.webkit.WebChromeClient;
75 import android.webkit.WebResourceResponse;
76 import android.webkit.WebSettings;
77 import android.webkit.WebStorage;
78 import android.webkit.WebView;
79 import android.webkit.WebViewClient;
80 import android.webkit.WebViewDatabase;
81 import android.widget.ArrayAdapter;
82 import android.widget.CursorAdapter;
83 import android.widget.EditText;
84 import android.widget.FrameLayout;
85 import android.widget.ImageView;
86 import android.widget.LinearLayout;
87 import android.widget.ListView;
88 import android.widget.ProgressBar;
89 import android.widget.RadioButton;
90 import android.widget.RelativeLayout;
91 import android.widget.TextView;
93 import androidx.annotation.NonNull;
94 import androidx.appcompat.app.ActionBar;
95 import androidx.appcompat.app.ActionBarDrawerToggle;
96 import androidx.appcompat.app.AppCompatActivity;
97 import androidx.appcompat.widget.Toolbar;
98 import androidx.coordinatorlayout.widget.CoordinatorLayout;
99 import androidx.core.app.ActivityCompat;
100 import androidx.core.content.ContextCompat;
101 import androidx.core.view.GravityCompat;
102 import androidx.drawerlayout.widget.DrawerLayout;
103 import androidx.fragment.app.DialogFragment;
104 import androidx.fragment.app.FragmentManager;
105 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
106 import androidx.viewpager.widget.ViewPager;
108 import com.google.android.material.appbar.AppBarLayout;
109 import com.google.android.material.floatingactionbutton.FloatingActionButton;
110 import com.google.android.material.navigation.NavigationView;
111 import com.google.android.material.snackbar.Snackbar;
112 import com.google.android.material.tabs.TabLayout;
114 import com.stoutner.privacybrowser.BuildConfig;
115 import com.stoutner.privacybrowser.R;
116 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
117 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
118 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
119 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
120 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
121 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
122 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
123 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
124 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
125 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
126 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
127 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
128 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
129 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
130 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
131 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
132 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
133 import com.stoutner.privacybrowser.helpers.AdHelper;
134 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
135 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
136 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
137 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
138 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
139 import com.stoutner.privacybrowser.views.NestedScrollWebView;
141 import java.io.ByteArrayInputStream;
142 import java.io.ByteArrayOutputStream;
144 import java.io.IOException;
145 import java.io.UnsupportedEncodingException;
146 import java.net.MalformedURLException;
148 import java.net.URLDecoder;
149 import java.net.URLEncoder;
150 import java.util.ArrayList;
151 import java.util.Date;
152 import java.util.HashMap;
153 import java.util.HashSet;
154 import java.util.List;
155 import java.util.Map;
156 import java.util.Set;
158 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
159 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
160 DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
161 EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, PopulateBlocklists.PopulateBlocklistsListener, WebViewTabFragment.NewTabListener {
163 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
164 public static String orbotStatus;
166 // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`.
167 public static WebViewPagerAdapter webViewPagerAdapter;
169 // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`. They are used in `onRestart()`.
170 public static boolean loadUrlOnRestart;
171 public static String urlToLoadOnRestart;
173 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
174 public static boolean restartFromBookmarksActivity;
176 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
177 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
178 public static String currentBookmarksFolder;
180 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
181 public final static int UNRECOGNIZED_USER_AGENT = -1;
182 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
183 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
184 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
185 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
186 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
190 // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
191 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`.
192 private NestedScrollWebView currentWebView;
194 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
195 private final Map<String, String> customHeaders = new HashMap<>();
197 // The search URL is set in `applyProxyThroughOrbot()` and used in `onCreate()`, `onNewIntent()`, `loadURLFromTextBox()`, and `initializeWebView()`.
198 private String searchURL;
200 // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
201 private Menu optionsMenu;
203 // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
204 private ArrayList<List<String[]>> easyList;
205 private ArrayList<List<String[]>> easyPrivacy;
206 private ArrayList<List<String[]>> fanboysAnnoyanceList;
207 private ArrayList<List<String[]>> fanboysSocialList;
208 private ArrayList<List<String[]>> ultraPrivacy;
210 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
211 private String webViewDefaultUserAgent;
213 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
214 private boolean proxyThroughOrbot;
216 // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
217 private boolean incognitoModeEnabled;
219 // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
220 private boolean fullScreenBrowsingModeEnabled;
222 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
223 private boolean inFullScreenBrowsingMode;
225 // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
226 private boolean hideAppBar;
227 private boolean scrollAppBar;
229 // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
230 private boolean loadingNewIntent;
232 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
233 private boolean reapplyDomainSettingsOnRestart;
235 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
236 private boolean reapplyAppSettingsOnRestart;
238 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
239 private boolean displayingFullScreenVideo;
241 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
242 private BroadcastReceiver orbotStatusBroadcastReceiver;
244 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
245 private boolean waitingForOrbot;
247 // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
248 private ActionBarDrawerToggle actionBarDrawerToggle;
250 // The color spans are used in `onCreate()` and `highlightUrlText()`.
251 private ForegroundColorSpan redColorSpan;
252 private ForegroundColorSpan initialGrayColorSpan;
253 private ForegroundColorSpan finalGrayColorSpan;
255 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
256 private int drawerHeaderPaddingLeftAndRight;
257 private int drawerHeaderPaddingTop;
258 private int drawerHeaderPaddingBottom;
260 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
261 // and `loadBookmarksFolder()`.
262 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
264 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
265 private Cursor bookmarksCursor;
267 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
268 private CursorAdapter bookmarksCursorAdapter;
270 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
271 private String oldFolderNameString;
273 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
274 private ValueCallback<Uri[]> fileChooserCallback;
276 // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
277 private int defaultProgressViewStartOffset;
278 private int defaultProgressViewEndOffset;
280 // The swipe refresh layout top padding is used when exiting full screen browsing mode. It is used in an inner class in `initializeWebView()`.
281 private int swipeRefreshLayoutPaddingTop;
283 // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
284 private boolean sanitizeGoogleAnalytics;
285 private boolean sanitizeFacebookClickIds;
286 private boolean sanitizeTwitterAmpRedirects;
288 // The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
289 private String downloadUrl;
290 private String downloadContentDisposition;
291 private long downloadContentLength;
293 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
294 private String downloadImageUrl;
296 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
297 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
298 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
301 // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
302 @SuppressLint("ClickableViewAccessibility")
303 protected void onCreate(Bundle savedInstanceState) {
304 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
305 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
307 // Get a handle for the shared preferences.
308 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
310 // Get the theme and screenshot preferences.
311 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
312 boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
314 // Disable screenshots if not allowed.
315 if (!allowScreenshots) {
316 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
319 // Set the activity theme.
321 setTheme(R.style.PrivacyBrowserDark);
323 setTheme(R.style.PrivacyBrowserLight);
326 // Run the default commands.
327 super.onCreate(savedInstanceState);
329 // Set the content view.
330 setContentView(R.layout.main_framelayout);
332 // Get handles for the views that need to be modified.
333 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
334 Toolbar toolbar = findViewById(R.id.toolbar);
335 ViewPager webViewPager = findViewById(R.id.webviewpager);
337 // Set the action bar. `SupportActionBar` must be used until the minimum API is >= 21.
338 setSupportActionBar(toolbar);
340 // Get a handle for the action bar.
341 ActionBar actionBar = getSupportActionBar();
343 // This is needed to get rid of the Android Studio warning that the action bar might be null.
344 assert actionBar != null;
346 // Add the custom layout, which shows the URL text bar.
347 actionBar.setCustomView(R.layout.url_app_bar);
348 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
350 // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded.
351 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
353 // Create the hamburger icon at the start of the AppBar.
354 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
356 // Initialize the web view pager adapter.
357 webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
359 // Set the pager adapter on the web view pager.
360 webViewPager.setAdapter(webViewPagerAdapter);
362 // Store up to 100 tabs in memory.
363 webViewPager.setOffscreenPageLimit(100);
365 // Populate the blocklists.
366 new PopulateBlocklists(this, this).execute();
370 protected void onNewIntent(Intent intent) {
371 // Replace the intent that started the app with this one.
374 // Process the intent here if Privacy Browser is fully initialized. If the process has been killed by the system while sitting in the background, this will be handled in `initializeWebView()`.
375 if (ultraPrivacy != null) {
376 // Get the information from the intent.
377 String intentAction = intent.getAction();
378 Uri intentUriData = intent.getData();
380 // Determine if this is a web search.
381 boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
383 // Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null.
384 if (intentUriData != null || isWebSearch) {
385 // Get the shared preferences.
386 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
388 // Create a URL string.
391 // If the intent action is a web search, perform the search.
393 // Create an encoded URL string.
394 String encodedUrlString;
396 // Sanitize the search input and convert it to a search.
398 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
399 } catch (UnsupportedEncodingException exception) {
400 encodedUrlString = "";
403 // Add the base search URL.
404 url = searchURL + encodedUrlString;
405 } else { // The intent should contain a URL.
406 // Set the intent data as the URL.
407 url = intentUriData.toString();
410 // Add a new tab if specified in the preferences.
411 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
412 // Set the loading new intent flag.
413 loadingNewIntent = true;
417 } else { // Load the URL in the current tab.
422 // Get a handle for the drawer layout.
423 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
425 // Close the navigation drawer if it is open.
426 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
427 drawerLayout.closeDrawer(GravityCompat.START);
430 // Close the bookmarks drawer if it is open.
431 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
432 drawerLayout.closeDrawer(GravityCompat.END);
439 public void onRestart() {
440 // Run the default commands.
443 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
444 if (proxyThroughOrbot) {
445 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
446 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
448 // Send the intent to the Orbot package.
449 orbotIntent.setPackage("org.torproject.android");
452 sendBroadcast(orbotIntent);
455 // Apply the app settings if returning from the Settings activity.
456 if (reapplyAppSettingsOnRestart) {
457 // Reset the reapply app settings on restart tracker.
458 reapplyAppSettingsOnRestart = false;
460 // Apply the app settings.
464 // Apply the domain settings if returning from the settings or domains activity.
465 if (reapplyDomainSettingsOnRestart) {
466 // Reset the reapply domain settings on restart tracker.
467 reapplyDomainSettingsOnRestart = false;
469 // Reapply the domain settings for each tab.
470 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
471 // Get the WebView tab fragment.
472 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
474 // Get the fragment view.
475 View fragmentView = webViewTabFragment.getView();
477 // Only reload the WebViews if they exist.
478 if (fragmentView != null) {
479 // Get the nested scroll WebView from the tab fragment.
480 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
482 // Reset the current domain name so the domain settings will be reapplied.
483 nestedScrollWebView.resetCurrentDomainName();
485 // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
486 if (nestedScrollWebView.getUrl() != null) {
487 applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
493 // Load the URL on restart (used when loading a bookmark).
494 if (loadUrlOnRestart) {
495 // Load the specified URL.
496 loadUrl(urlToLoadOnRestart);
498 // Reset the load on restart tracker.
499 loadUrlOnRestart = false;
502 // Update the bookmarks drawer if returning from the Bookmarks activity.
503 if (restartFromBookmarksActivity) {
504 // Get a handle for the drawer layout.
505 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
507 // Close the bookmarks drawer.
508 drawerLayout.closeDrawer(GravityCompat.END);
510 // Reload the bookmarks drawer.
511 loadBookmarksFolder();
513 // Reset `restartFromBookmarksActivity`.
514 restartFromBookmarksActivity = false;
517 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
518 updatePrivacyIcons(true);
521 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
523 public void onResume() {
524 // Run the default commands.
527 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
528 // Get the WebView tab fragment.
529 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
531 // Get the fragment view.
532 View fragmentView = webViewTabFragment.getView();
534 // Only resume the WebViews if they exist (they won't when the app is first created).
535 if (fragmentView != null) {
536 // Get the nested scroll WebView from the tab fragment.
537 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
539 // Resume the nested scroll WebView JavaScript timers.
540 nestedScrollWebView.resumeTimers();
542 // Resume the nested scroll WebView.
543 nestedScrollWebView.onResume();
547 // Display a message to the user if waiting for Orbot.
548 if (waitingForOrbot && !orbotStatus.equals("ON")) {
549 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
550 currentWebView.getSettings().setUseWideViewPort(false);
552 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
553 currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
556 if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
557 // Get a handle for the root frame layouts.
558 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
560 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
561 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
563 /* Hide the system bars.
564 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
565 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
566 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
567 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
569 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
570 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
571 } else if (BuildConfig.FLAVOR.contentEquals("free")) { // Resume the adView for the free flavor.
573 AdHelper.resumeAd(findViewById(R.id.adview));
578 public void onPause() {
579 // Run the default commands.
582 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
583 // Get the WebView tab fragment.
584 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
586 // Get the fragment view.
587 View fragmentView = webViewTabFragment.getView();
589 // Only pause the WebViews if they exist (they won't when the app is first created).
590 if (fragmentView != null) {
591 // Get the nested scroll WebView from the tab fragment.
592 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
594 // Pause the nested scroll WebView.
595 nestedScrollWebView.onPause();
597 // Pause the nested scroll WebView JavaScript timers.
598 nestedScrollWebView.pauseTimers();
602 // Pause the ad or it will continue to consume resources in the background on the free flavor.
603 if (BuildConfig.FLAVOR.contentEquals("free")) {
605 AdHelper.pauseAd(findViewById(R.id.adview));
610 public void onDestroy() {
611 // Unregister the Orbot status broadcast receiver.
612 this.unregisterReceiver(orbotStatusBroadcastReceiver);
614 // Close the bookmarks cursor and database.
615 bookmarksCursor.close();
616 bookmarksDatabaseHelper.close();
618 // Run the default commands.
623 public boolean onCreateOptionsMenu(Menu menu) {
624 // Inflate the menu. This adds items to the action bar if it is present.
625 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
627 // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
630 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
631 updatePrivacyIcons(false);
633 // Get handles for the menu items.
634 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
635 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
636 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
637 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
638 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
639 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
640 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
642 // Only display third-party cookies if API >= 21
643 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
645 // Only display the form data menu items if the API < 26.
646 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
647 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
649 // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
650 clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
652 // Only show Ad Consent if this is the free flavor.
653 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
655 // Get the shared preferences.
656 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
658 // Get the dark theme and app bar preferences..
659 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
660 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
662 // Set the status of the additional app bar icons. Setting the refresh menu item to `SHOW_AS_ACTION_ALWAYS` makes it appear even on small devices like phones.
663 if (displayAdditionalAppBarIcons) {
664 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
665 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
666 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
667 } else { //Do not display the additional icons.
668 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
669 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
670 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
673 // Replace Refresh with Stop if a URL is already loading.
674 if (currentWebView != null && currentWebView.getProgress() != 100) {
676 refreshMenuItem.setTitle(R.string.stop);
678 // If the icon is displayed in the AppBar, set it according to the theme.
679 if (displayAdditionalAppBarIcons) {
681 refreshMenuItem.setIcon(R.drawable.close_dark);
683 refreshMenuItem.setIcon(R.drawable.close_light);
693 public boolean onPrepareOptionsMenu(Menu menu) {
694 // Get handles for the menu items.
695 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
696 MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
697 MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
698 MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
699 MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
700 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
701 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
702 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
703 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
704 MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
705 MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
706 MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
707 MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
708 MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
709 MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
710 MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
711 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
712 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
713 MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
714 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
715 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
716 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
718 // Get a handle for the cookie manager.
719 CookieManager cookieManager = CookieManager.getInstance();
721 // Initialize the current user agent string and the font size.
722 String currentUserAgent = getString(R.string.user_agent_privacy_browser);
725 // Set items that require the current web view to be populated. It will be null when the program is first opened, as `onPrepareOptionsMenu()` is called before the first WebView is initialized.
726 if (currentWebView != null) {
727 // Set the add or edit domain text.
728 if (currentWebView.getDomainSettingsApplied()) {
729 addOrEditDomain.setTitle(R.string.edit_domain_settings);
731 addOrEditDomain.setTitle(R.string.add_domain_settings);
734 // Get the current user agent from the WebView.
735 currentUserAgent = currentWebView.getSettings().getUserAgentString();
737 // Get the current font size from the
738 fontSize = currentWebView.getSettings().getTextZoom();
740 // Set the status of the menu item checkboxes.
741 domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
742 saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
743 easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
744 easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
745 fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
746 fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
747 ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
748 blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
749 swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
750 wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
751 displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
752 nightModeMenuItem.setChecked(currentWebView.getNightMode());
754 // Initialize the display names for the blocklists with the number of blocked requests.
755 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
756 easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
757 easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
758 fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
759 fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
760 ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
761 blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
763 // Only modify third-party cookies if the API >= 21.
764 if (Build.VERSION.SDK_INT >= 21) {
765 // Set the status of the third-party cookies checkbox.
766 thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
768 // Enable third-party cookies if first-party cookies are enabled.
769 thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
772 // Enable DOM Storage if JavaScript is enabled.
773 domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
776 // Set the status of the menu item checkboxes.
777 firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
778 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
780 // Enable Clear Cookies if there are any.
781 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
783 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`, which links to `/data/data/com.stoutner.privacybrowser.standard`.
784 String privateDataDirectoryString = getApplicationInfo().dataDir;
786 // Get a count of the number of files in the Local Storage directory.
787 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
788 int localStorageDirectoryNumberOfFiles = 0;
789 if (localStorageDirectory.exists()) {
790 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
793 // Get a count of the number of files in the IndexedDB directory.
794 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
795 int indexedDBDirectoryNumberOfFiles = 0;
796 if (indexedDBDirectory.exists()) {
797 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
800 // Enable Clear DOM Storage if there is any.
801 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
803 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
804 if (Build.VERSION.SDK_INT < 26) {
805 // Get the WebView database.
806 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
808 // Enable the clear form data menu item if there is anything to clear.
809 clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
812 // Enable Clear Data if any of the submenu items are enabled.
813 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
815 // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
816 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
818 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
819 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
820 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
821 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
822 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
823 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
824 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
825 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
826 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
827 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
828 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
829 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
830 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
831 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
832 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
833 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
834 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
835 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
836 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
837 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
838 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
839 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
840 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
841 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
842 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
843 } else { // Custom user agent.
844 menu.findItem(R.id.user_agent_custom).setChecked(true);
847 // Instantiate the font size title and the selected font size menu item.
848 String fontSizeTitle;
849 MenuItem selectedFontSizeMenuItem;
851 // Prepare the font size title and current size menu item.
854 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
855 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
859 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
860 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
864 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
865 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
869 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
870 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
874 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
875 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
879 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
880 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
884 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
885 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
889 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
890 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
894 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
895 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
899 // Set the font size title and select the current size menu item.
900 fontSizeMenuItem.setTitle(fontSizeTitle);
901 selectedFontSizeMenuItem.setChecked(true);
903 // Run all the other default commands.
904 super.onPrepareOptionsMenu(menu);
911 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
912 @SuppressLint("SetJavaScriptEnabled")
913 public boolean onOptionsItemSelected(MenuItem menuItem) {
914 // Reenter full screen browsing mode if it was interrupted by the options menu. <https://redmine.stoutner.com/issues/389>
915 if (inFullScreenBrowsingMode) {
916 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
917 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
919 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
921 /* Hide the system bars.
922 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
923 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
924 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
925 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
927 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
928 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
931 // Get the selected menu item ID.
932 int menuItemId = menuItem.getItemId();
934 // Get a handle for the shared preferences.
935 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
937 // Get a handle for the cookie manager.
938 CookieManager cookieManager = CookieManager.getInstance();
940 // Run the commands that correlate to the selected menu item.
941 switch (menuItemId) {
942 case R.id.toggle_javascript:
943 // Toggle the JavaScript status.
944 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
946 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
947 updatePrivacyIcons(true);
949 // Display a `Snackbar`.
950 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled.
951 Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
952 } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled.
953 Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
954 } else { // Privacy mode.
955 Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
958 // Reload the current WebView.
959 currentWebView.reload();
962 case R.id.add_or_edit_domain:
963 if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings.
964 // Reapply the domain settings on returning to `MainWebViewActivity`.
965 reapplyDomainSettingsOnRestart = true;
967 // Create an intent to launch the domains activity.
968 Intent domainsIntent = new Intent(this, DomainsActivity.class);
970 // Add the extra information to the intent.
971 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
972 domainsIntent.putExtra("close_on_back", true);
973 domainsIntent.putExtra("current_url", currentWebView.getUrl());
975 // Get the current certificate.
976 SslCertificate sslCertificate = currentWebView.getCertificate();
978 // Check to see if the SSL certificate is populated.
979 if (sslCertificate != null) {
980 // Extract the certificate to strings.
981 String issuedToCName = sslCertificate.getIssuedTo().getCName();
982 String issuedToOName = sslCertificate.getIssuedTo().getOName();
983 String issuedToUName = sslCertificate.getIssuedTo().getUName();
984 String issuedByCName = sslCertificate.getIssuedBy().getCName();
985 String issuedByOName = sslCertificate.getIssuedBy().getOName();
986 String issuedByUName = sslCertificate.getIssuedBy().getUName();
987 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
988 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
990 // Add the certificate to the intent.
991 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
992 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
993 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
994 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
995 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
996 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
997 domainsIntent.putExtra("ssl_start_date", startDateLong);
998 domainsIntent.putExtra("ssl_end_date", endDateLong);
1001 // Check to see if the current IP addresses have been received.
1002 if (currentWebView.hasCurrentIpAddresses()) {
1003 // Add the current IP addresses to the intent.
1004 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1008 startActivity(domainsIntent);
1009 } else { // Add a new domain.
1010 // Apply the new domain settings on returning to `MainWebViewActivity`.
1011 reapplyDomainSettingsOnRestart = true;
1013 // Get the current domain
1014 Uri currentUri = Uri.parse(currentWebView.getUrl());
1015 String currentDomain = currentUri.getHost();
1017 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1018 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1020 // Create the domain and store the database ID.
1021 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1023 // Create an intent to launch the domains activity.
1024 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1026 // Add the extra information to the intent.
1027 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1028 domainsIntent.putExtra("close_on_back", true);
1029 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1031 // Get the current certificate.
1032 SslCertificate sslCertificate = currentWebView.getCertificate();
1034 // Check to see if the SSL certificate is populated.
1035 if (sslCertificate != null) {
1036 // Extract the certificate to strings.
1037 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1038 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1039 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1040 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1041 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1042 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1043 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1044 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1046 // Add the certificate to the intent.
1047 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1048 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1049 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1050 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1051 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1052 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1053 domainsIntent.putExtra("ssl_start_date", startDateLong);
1054 domainsIntent.putExtra("ssl_end_date", endDateLong);
1057 // Check to see if the current IP addresses have been received.
1058 if (currentWebView.hasCurrentIpAddresses()) {
1059 // Add the current IP addresses to the intent.
1060 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1064 startActivity(domainsIntent);
1068 case R.id.toggle_first_party_cookies:
1069 // Switch the first-party cookie status.
1070 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1072 // Store the first-party cookie status.
1073 currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
1075 // Update the menu checkbox.
1076 menuItem.setChecked(cookieManager.acceptCookie());
1078 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1079 updatePrivacyIcons(true);
1081 // Display a snackbar.
1082 if (cookieManager.acceptCookie()) { // First-party cookies are enabled.
1083 Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1084 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
1085 Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1086 } else { // Privacy mode.
1087 Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1090 // Reload the current WebView.
1091 currentWebView.reload();
1094 case R.id.toggle_third_party_cookies:
1095 if (Build.VERSION.SDK_INT >= 21) {
1096 // Switch the status of thirdPartyCookiesEnabled.
1097 cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
1099 // Update the menu checkbox.
1100 menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1102 // Display a snackbar.
1103 if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
1104 Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1106 Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1109 // Reload the current WebView.
1110 currentWebView.reload();
1111 } // Else do nothing because SDK < 21.
1114 case R.id.toggle_dom_storage:
1115 // Toggle the status of domStorageEnabled.
1116 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1118 // Update the menu checkbox.
1119 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1121 // Update the privacy icon. `true` refreshes the app bar icons.
1122 updatePrivacyIcons(true);
1124 // Display a snackbar.
1125 if (currentWebView.getSettings().getDomStorageEnabled()) {
1126 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1128 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1131 // Reload the current WebView.
1132 currentWebView.reload();
1135 // Form data can be removed once the minimum API >= 26.
1136 case R.id.toggle_save_form_data:
1137 // Switch the status of saveFormDataEnabled.
1138 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1140 // Update the menu checkbox.
1141 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1143 // Display a snackbar.
1144 if (currentWebView.getSettings().getSaveFormData()) {
1145 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1147 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1150 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1151 updatePrivacyIcons(true);
1153 // Reload the current WebView.
1154 currentWebView.reload();
1157 case R.id.clear_cookies:
1158 Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1159 .setAction(R.string.undo, v -> {
1160 // Do nothing because everything will be handled by `onDismissed()` below.
1162 .addCallback(new Snackbar.Callback() {
1163 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1165 public void onDismissed(Snackbar snackbar, int event) {
1166 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1167 // Delete the cookies, which command varies by SDK.
1168 if (Build.VERSION.SDK_INT < 21) {
1169 cookieManager.removeAllCookie();
1171 cookieManager.removeAllCookies(null);
1179 case R.id.clear_dom_storage:
1180 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1181 .setAction(R.string.undo, v -> {
1182 // Do nothing because everything will be handled by `onDismissed()` below.
1184 .addCallback(new Snackbar.Callback() {
1185 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1187 public void onDismissed(Snackbar snackbar, int event) {
1188 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1189 // Delete the DOM Storage.
1190 WebStorage webStorage = WebStorage.getInstance();
1191 webStorage.deleteAllData();
1193 // Initialize a handler to manually delete the DOM storage files and directories.
1194 Handler deleteDomStorageHandler = new Handler();
1196 // Setup a runnable to manually delete the DOM storage files and directories.
1197 Runnable deleteDomStorageRunnable = () -> {
1199 // Get a handle for the runtime.
1200 Runtime runtime = Runtime.getRuntime();
1202 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1203 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1204 String privateDataDirectoryString = getApplicationInfo().dataDir;
1206 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1207 Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1209 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1210 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1211 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1212 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1213 Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1215 // Wait for the processes to finish.
1216 deleteLocalStorageProcess.waitFor();
1217 deleteIndexProcess.waitFor();
1218 deleteQuotaManagerProcess.waitFor();
1219 deleteQuotaManagerJournalProcess.waitFor();
1220 deleteDatabasesProcess.waitFor();
1221 } catch (Exception exception) {
1222 // Do nothing if an error is thrown.
1226 // Manually delete the DOM storage files after 200 milliseconds.
1227 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1234 // Form data can be remove once the minimum API >= 26.
1235 case R.id.clear_form_data:
1236 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1237 .setAction(R.string.undo, v -> {
1238 // Do nothing because everything will be handled by `onDismissed()` below.
1240 .addCallback(new Snackbar.Callback() {
1241 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1243 public void onDismissed(Snackbar snackbar, int event) {
1244 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1245 // Delete the form data.
1246 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1247 mainWebViewDatabase.clearFormData();
1255 // Toggle the EasyList status.
1256 currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1258 // Update the menu checkbox.
1259 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1261 // Reload the current WebView.
1262 currentWebView.reload();
1265 case R.id.easyprivacy:
1266 // Toggle the EasyPrivacy status.
1267 currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1269 // Update the menu checkbox.
1270 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1272 // Reload the current WebView.
1273 currentWebView.reload();
1276 case R.id.fanboys_annoyance_list:
1277 // Toggle Fanboy's Annoyance List status.
1278 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1280 // Update the menu checkbox.
1281 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1283 // Update the staus of Fanboy's Social Blocking List.
1284 MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
1285 fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1287 // Reload the current WebView.
1288 currentWebView.reload();
1291 case R.id.fanboys_social_blocking_list:
1292 // Toggle Fanboy's Social Blocking List status.
1293 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1295 // Update the menu checkbox.
1296 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1298 // Reload the current WebView.
1299 currentWebView.reload();
1302 case R.id.ultraprivacy:
1303 // Toggle the UltraPrivacy status.
1304 currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1306 // Update the menu checkbox.
1307 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1309 // Reload the current WebView.
1310 currentWebView.reload();
1313 case R.id.block_all_third_party_requests:
1314 //Toggle the third-party requests blocker status.
1315 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1317 // Update the menu checkbox.
1318 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1320 // Reload the current WebView.
1321 currentWebView.reload();
1324 case R.id.user_agent_privacy_browser:
1325 // Update the user agent.
1326 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1328 // Reload the current WebView.
1329 currentWebView.reload();
1332 case R.id.user_agent_webview_default:
1333 // Update the user agent.
1334 currentWebView.getSettings().setUserAgentString("");
1336 // Reload the current WebView.
1337 currentWebView.reload();
1340 case R.id.user_agent_firefox_on_android:
1341 // Update the user agent.
1342 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1344 // Reload the current WebView.
1345 currentWebView.reload();
1348 case R.id.user_agent_chrome_on_android:
1349 // Update the user agent.
1350 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1352 // Reload the current WebView.
1353 currentWebView.reload();
1356 case R.id.user_agent_safari_on_ios:
1357 // Update the user agent.
1358 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1360 // Reload the current WebView.
1361 currentWebView.reload();
1364 case R.id.user_agent_firefox_on_linux:
1365 // Update the user agent.
1366 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1368 // Reload the current WebView.
1369 currentWebView.reload();
1372 case R.id.user_agent_chromium_on_linux:
1373 // Update the user agent.
1374 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1376 // Reload the current WebView.
1377 currentWebView.reload();
1380 case R.id.user_agent_firefox_on_windows:
1381 // Update the user agent.
1382 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1384 // Reload the current WebView.
1385 currentWebView.reload();
1388 case R.id.user_agent_chrome_on_windows:
1389 // Update the user agent.
1390 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1392 // Reload the current WebView.
1393 currentWebView.reload();
1396 case R.id.user_agent_edge_on_windows:
1397 // Update the user agent.
1398 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1400 // Reload the current WebView.
1401 currentWebView.reload();
1404 case R.id.user_agent_internet_explorer_on_windows:
1405 // Update the user agent.
1406 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1408 // Reload the current WebView.
1409 currentWebView.reload();
1412 case R.id.user_agent_safari_on_macos:
1413 // Update the user agent.
1414 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1416 // Reload the current WebView.
1417 currentWebView.reload();
1420 case R.id.user_agent_custom:
1421 // Update the user agent.
1422 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1424 // Reload the current WebView.
1425 currentWebView.reload();
1428 case R.id.font_size_twenty_five_percent:
1429 currentWebView.getSettings().setTextZoom(25);
1432 case R.id.font_size_fifty_percent:
1433 currentWebView.getSettings().setTextZoom(50);
1436 case R.id.font_size_seventy_five_percent:
1437 currentWebView.getSettings().setTextZoom(75);
1440 case R.id.font_size_one_hundred_percent:
1441 currentWebView.getSettings().setTextZoom(100);
1444 case R.id.font_size_one_hundred_twenty_five_percent:
1445 currentWebView.getSettings().setTextZoom(125);
1448 case R.id.font_size_one_hundred_fifty_percent:
1449 currentWebView.getSettings().setTextZoom(150);
1452 case R.id.font_size_one_hundred_seventy_five_percent:
1453 currentWebView.getSettings().setTextZoom(175);
1456 case R.id.font_size_two_hundred_percent:
1457 currentWebView.getSettings().setTextZoom(200);
1460 case R.id.swipe_to_refresh:
1461 // Toggle the stored status of swipe to refresh.
1462 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1464 // Get a handle for the swipe refresh layout.
1465 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
1467 // Update the swipe refresh layout.
1468 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
1469 // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
1470 swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
1471 } else { // Swipe to refresh is disabled.
1472 // Disable the swipe refresh layout.
1473 swipeRefreshLayout.setEnabled(false);
1477 case R.id.wide_viewport:
1478 // Toggle the viewport.
1479 currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1482 case R.id.display_images:
1483 if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
1484 // Disable loading of images.
1485 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1487 // Reload the website to remove existing images.
1488 currentWebView.reload();
1489 } else { // Images are not currently loaded automatically.
1490 // Enable loading of images. Missing images will be loaded without the need for a reload.
1491 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1495 case R.id.night_mode:
1496 // Toggle night mode.
1497 currentWebView.setNightMode(!currentWebView.getNightMode());
1499 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
1500 if (currentWebView.getNightMode()) { // Night mode is enabled, which requires JavaScript.
1501 // Enable JavaScript.
1502 currentWebView.getSettings().setJavaScriptEnabled(true);
1503 } else if (currentWebView.getDomainSettingsApplied()) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
1504 // Apply the JavaScript preference that was stored the last time domain settings were loaded.
1505 currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
1506 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
1507 // Apply the JavaScript preference.
1508 currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
1511 // Update the privacy icons.
1512 updatePrivacyIcons(false);
1514 // Reload the website.
1515 currentWebView.reload();
1518 case R.id.find_on_page:
1519 // Get a handle for the views.
1520 Toolbar toolbar = findViewById(R.id.toolbar);
1521 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1522 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1524 // Set the minimum height of the find on page linear layout to match the toolbar.
1525 findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1527 // Hide the toolbar.
1528 toolbar.setVisibility(View.GONE);
1530 // Show the find on page linear layout.
1531 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1533 // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
1534 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1535 findOnPageEditText.postDelayed(() -> {
1536 // Set the focus on `findOnPageEditText`.
1537 findOnPageEditText.requestFocus();
1539 // Get a handle for the input method manager.
1540 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1542 // Remove the lint warning below that the input method manager might be null.
1543 assert inputMethodManager != null;
1545 // Display the keyboard. `0` sets no input flags.
1546 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1551 // Get a print manager instance.
1552 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1554 // Remove the lint error below that print manager might be null.
1555 assert printManager != null;
1557 // Create a print document adapter from the current WebView.
1558 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1560 // Print the document.
1561 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1564 case R.id.add_to_homescreen:
1565 // Instantiate the create home screen shortcut dialog.
1566 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1567 currentWebView.getFavoriteOrDefaultIcon());
1569 // Show the create home screen shortcut dialog.
1570 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1573 case R.id.view_source:
1574 // Create an intent to launch the view source activity.
1575 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1577 // Add the variables to the intent.
1578 viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
1579 viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
1582 startActivity(viewSourceIntent);
1585 case R.id.share_url:
1586 // Setup the share string.
1587 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1589 // Create the share intent.
1590 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1591 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1592 shareIntent.setType("text/plain");
1595 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1598 case R.id.open_with_app:
1599 openWithApp(currentWebView.getUrl());
1602 case R.id.open_with_browser:
1603 openWithBrowser(currentWebView.getUrl());
1606 case R.id.proxy_through_orbot:
1607 // Toggle the proxy through Orbot variable.
1608 proxyThroughOrbot = !proxyThroughOrbot;
1610 // Apply the proxy through Orbot settings.
1611 applyProxyThroughOrbot(true);
1615 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
1616 // Reload the current WebView.
1617 currentWebView.reload();
1618 } else { // The stop button was pushed.
1619 // Stop the loading of the WebView.
1620 currentWebView.stopLoading();
1624 case R.id.ad_consent:
1625 // Display the ad consent dialog.
1626 DialogFragment adConsentDialogFragment = new AdConsentDialog();
1627 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1631 // Don't consume the event.
1632 return super.onOptionsItemSelected(menuItem);
1636 // removeAllCookies is deprecated, but it is required for API < 21.
1638 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1639 // Get the menu item ID.
1640 int menuItemId = menuItem.getItemId();
1642 // Get a handle for the shared preferences.
1643 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1645 // Run the commands that correspond to the selected menu item.
1646 switch (menuItemId) {
1647 case R.id.clear_and_exit:
1648 // Clear and exit Privacy Browser.
1653 // Select the homepage based on the proxy through Orbot status.
1654 if (proxyThroughOrbot) {
1655 // Load the Tor homepage.
1656 loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
1658 // Load the normal homepage.
1659 loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1664 if (currentWebView.canGoBack()) {
1665 // Reset the current domain name so that navigation works if third-party requests are blocked.
1666 currentWebView.resetCurrentDomainName();
1668 // Set navigating history so that the domain settings are applied when the new URL is loaded.
1669 currentWebView.setNavigatingHistory(true);
1671 // Load the previous website in the history.
1672 currentWebView.goBack();
1677 if (currentWebView.canGoForward()) {
1678 // Reset the current domain name so that navigation works if third-party requests are blocked.
1679 currentWebView.resetCurrentDomainName();
1681 // Set navigating history so that the domain settings are applied when the new URL is loaded.
1682 currentWebView.setNavigatingHistory(true);
1684 // Load the next website in the history.
1685 currentWebView.goForward();
1690 // Instantiate the URL history dialog.
1691 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
1693 // Show the URL history dialog.
1694 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
1698 // Populate the resource requests.
1699 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
1701 // Create an intent to launch the Requests activity.
1702 Intent requestsIntent = new Intent(this, RequestsActivity.class);
1704 // Add the block third-party requests status to the intent.
1705 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1708 startActivity(requestsIntent);
1711 case R.id.downloads:
1712 // Launch the system Download Manager.
1713 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1715 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
1716 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1718 startActivity(downloadManagerIntent);
1722 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
1723 reapplyDomainSettingsOnRestart = true;
1725 // Launch the domains activity.
1726 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1728 // Add the extra information to the intent.
1729 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1731 // Get the current certificate.
1732 SslCertificate sslCertificate = currentWebView.getCertificate();
1734 // Check to see if the SSL certificate is populated.
1735 if (sslCertificate != null) {
1736 // Extract the certificate to strings.
1737 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1738 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1739 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1740 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1741 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1742 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1743 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1744 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1746 // Add the certificate to the intent.
1747 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1748 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1749 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1750 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1751 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1752 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1753 domainsIntent.putExtra("ssl_start_date", startDateLong);
1754 domainsIntent.putExtra("ssl_end_date", endDateLong);
1757 // Check to see if the current IP addresses have been received.
1758 if (currentWebView.hasCurrentIpAddresses()) {
1759 // Add the current IP addresses to the intent.
1760 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1764 startActivity(domainsIntent);
1768 // Set the flag to reapply app settings on restart when returning from Settings.
1769 reapplyAppSettingsOnRestart = true;
1771 // Set the flag to reapply the domain settings on restart when returning from Settings.
1772 reapplyDomainSettingsOnRestart = true;
1774 // Launch the settings activity.
1775 Intent settingsIntent = new Intent(this, SettingsActivity.class);
1776 startActivity(settingsIntent);
1779 case R.id.import_export:
1780 // Launch the import/export activity.
1781 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
1782 startActivity(importExportIntent);
1786 // Launch the logcat activity.
1787 Intent logcatIntent = new Intent(this, LogcatActivity.class);
1788 startActivity(logcatIntent);
1792 // Launch `GuideActivity`.
1793 Intent guideIntent = new Intent(this, GuideActivity.class);
1794 startActivity(guideIntent);
1798 // Create an intent to launch the about activity.
1799 Intent aboutIntent = new Intent(this, AboutActivity.class);
1801 // Create a string array for the blocklist versions.
1802 String[] blocklistVersions = new String[] {easyList.get(0).get(0)[0], easyPrivacy.get(0).get(0)[0], fanboysAnnoyanceList.get(0).get(0)[0], fanboysSocialList.get(0).get(0)[0],
1803 ultraPrivacy.get(0).get(0)[0]};
1805 // Add the blocklist versions to the intent.
1806 aboutIntent.putExtra("blocklist_versions", blocklistVersions);
1809 startActivity(aboutIntent);
1813 // Get a handle for the drawer layout.
1814 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1816 // Close the navigation drawer.
1817 drawerLayout.closeDrawer(GravityCompat.START);
1822 public void onPostCreate(Bundle savedInstanceState) {
1823 // Run the default commands.
1824 super.onPostCreate(savedInstanceState);
1826 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
1827 actionBarDrawerToggle.syncState();
1831 public void onConfigurationChanged(Configuration newConfig) {
1832 // Run the default commands.
1833 super.onConfigurationChanged(newConfig);
1835 // Get the status bar pixel size.
1836 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
1837 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
1839 // Get the resource density.
1840 float screenDensity = getResources().getDisplayMetrics().density;
1842 // Recalculate the drawer header padding.
1843 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
1844 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
1845 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
1847 // Reload the ad for the free flavor if not in full screen mode.
1848 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
1849 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
1850 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
1853 // `invalidateOptionsMenu` should recalculate the number of action buttons from the menu to display on the app bar, but it doesn't because of the this bug:
1854 // https://code.google.com/p/android/issues/detail?id=20493#c8
1855 // ActivityCompat.invalidateOptionsMenu(this);
1859 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
1860 // Store the hit test result.
1861 final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
1863 // Create the URL strings.
1864 final String imageUrl;
1865 final String linkUrl;
1867 // Get handles for the system managers.
1868 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1869 FragmentManager fragmentManager = getSupportFragmentManager();
1870 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1872 // Remove the lint errors below that the clipboard manager might be null.
1873 assert clipboardManager != null;
1875 // Process the link according to the type.
1876 switch (hitTestResult.getType()) {
1877 // `SRC_ANCHOR_TYPE` is a link.
1878 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1879 // Get the target URL.
1880 linkUrl = hitTestResult.getExtra();
1882 // Set the target URL as the title of the `ContextMenu`.
1883 menu.setHeaderTitle(linkUrl);
1885 // Add an Open in New Tab entry.
1886 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
1887 // Load the link URL in a new tab.
1892 // Add an Open with App entry.
1893 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
1894 openWithApp(linkUrl);
1898 // Add an Open with Browser entry.
1899 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
1900 openWithBrowser(linkUrl);
1904 // Add a Copy URL entry.
1905 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
1906 // Save the link URL in a `ClipData`.
1907 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
1909 // Set the `ClipData` as the clipboard's primary clip.
1910 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
1914 // Add a Download URL entry.
1915 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
1916 // Check if the download should be processed by an external app.
1917 if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app.
1918 openUrlWithExternalApp(linkUrl);
1919 } else { // Download with Android's download manager.
1920 // Check to see if the storage permission has already been granted.
1921 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
1922 // Store the variables for future use by `onRequestPermissionsResult()`.
1923 downloadUrl = linkUrl;
1924 downloadContentDisposition = "none";
1925 downloadContentLength = -1;
1927 // Show a dialog if the user has previously denied the permission.
1928 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
1929 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
1930 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
1932 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
1933 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
1934 } else { // Show the permission request directly.
1935 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
1936 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
1938 } else { // The storage permission has already been granted.
1939 // Get a handle for the download file alert dialog.
1940 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
1942 // Show the download file alert dialog.
1943 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
1949 // Add a Cancel entry, which by default closes the context menu.
1950 menu.add(R.string.cancel);
1953 case WebView.HitTestResult.EMAIL_TYPE:
1954 // Get the target URL.
1955 linkUrl = hitTestResult.getExtra();
1957 // Set the target URL as the title of the `ContextMenu`.
1958 menu.setHeaderTitle(linkUrl);
1960 // Add a Write Email entry.
1961 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
1962 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1963 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1965 // Parse the url and set it as the data for the `Intent`.
1966 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
1968 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
1969 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1972 startActivity(emailIntent);
1976 // Add a Copy Email Address entry.
1977 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
1978 // Save the email address in a `ClipData`.
1979 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
1981 // Set the `ClipData` as the clipboard's primary clip.
1982 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
1986 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1987 menu.add(R.string.cancel);
1990 // `IMAGE_TYPE` is an image. `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. Privacy Browser processes them the same.
1991 case WebView.HitTestResult.IMAGE_TYPE:
1992 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1993 // Get the image URL.
1994 imageUrl = hitTestResult.getExtra();
1996 // Set the image URL as the title of the context menu.
1997 menu.setHeaderTitle(imageUrl);
1999 // Add an Open in New Tab entry.
2000 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2001 // Load the image URL in a new tab.
2002 addNewTab(imageUrl);
2006 // Add a View Image entry.
2007 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2012 // Add a `Download Image` entry.
2013 menu.add(R.string.download_image).setOnMenuItemClickListener((MenuItem item) -> {
2014 // Check if the download should be processed by an external app.
2015 if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app.
2016 openUrlWithExternalApp(imageUrl);
2017 } else { // Download with Android's download manager.
2018 // Check to see if the storage permission has already been granted.
2019 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
2020 // Store the image URL for use by `onRequestPermissionResult()`.
2021 downloadImageUrl = imageUrl;
2023 // Show a dialog if the user has previously denied the permission.
2024 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
2025 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_IMAGE.
2026 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_IMAGE);
2028 // Show the download location permission alert dialog. The permission will be requested when the dialog is closed.
2029 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
2030 } else { // Show the permission request directly.
2031 // Request the permission. The download dialog will be launched by `onRequestPermissionResult().
2032 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
2034 } else { // The storage permission has already been granted.
2035 // Get a handle for the download image alert dialog.
2036 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(imageUrl);
2038 // Show the download image alert dialog.
2039 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
2045 // Add a `Copy URL` entry.
2046 menu.add(R.string.copy_url).setOnMenuItemClickListener(item -> {
2047 // Save the image URL in a `ClipData`.
2048 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2050 // Set the `ClipData` as the clipboard's primary clip.
2051 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2055 // Add an Open with App entry.
2056 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2057 openWithApp(imageUrl);
2061 // Add an Open with Browser entry.
2062 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2063 openWithBrowser(imageUrl);
2067 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
2068 menu.add(R.string.cancel);
2074 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2075 // Get a handle for the bookmarks list view.
2076 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2078 // Get the views from the dialog fragment.
2079 EditText createBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_name_edittext);
2080 EditText createBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.create_bookmark_url_edittext);
2082 // Extract the strings from the edit texts.
2083 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2084 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2086 // Create a favorite icon byte array output stream.
2087 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2089 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2090 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2092 // Convert the favorite icon byte array stream to a byte array.
2093 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2095 // Display the new bookmark below the current items in the (0 indexed) list.
2096 int newBookmarkDisplayOrder = bookmarksListView.getCount();
2098 // Create the bookmark.
2099 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2101 // Update the bookmarks cursor with the current contents of this folder.
2102 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2104 // Update the list view.
2105 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2107 // Scroll to the new bookmark.
2108 bookmarksListView.setSelection(newBookmarkDisplayOrder);
2112 public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2113 // Get a handle for the bookmarks list view.
2114 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2116 // Get handles for the views in the dialog fragment.
2117 EditText createFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.create_folder_name_edittext);
2118 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon_radiobutton);
2119 ImageView folderIconImageView = dialogFragment.getDialog().findViewById(R.id.create_folder_default_icon);
2121 // Get new folder name string.
2122 String folderNameString = createFolderNameEditText.getText().toString();
2124 // Create a folder icon bitmap.
2125 Bitmap folderIconBitmap;
2127 // Set the folder icon bitmap according to the dialog.
2128 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
2129 // Get the default folder icon drawable.
2130 Drawable folderIconDrawable = folderIconImageView.getDrawable();
2132 // Convert the folder icon drawable to a bitmap drawable.
2133 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2135 // Convert the folder icon bitmap drawable to a bitmap.
2136 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2137 } else { // Use the WebView favorite icon.
2138 // Copy the favorite icon bitmap to the folder icon bitmap.
2139 folderIconBitmap = favoriteIconBitmap;
2142 // Create a folder icon byte array output stream.
2143 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2145 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2146 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2148 // Convert the folder icon byte array stream to a byte array.
2149 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2151 // Move all the bookmarks down one in the display order.
2152 for (int i = 0; i < bookmarksListView.getCount(); i++) {
2153 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2154 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2157 // Create the folder, which will be placed at the top of the `ListView`.
2158 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2160 // Update the bookmarks cursor with the current contents of this folder.
2161 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2163 // Update the `ListView`.
2164 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2166 // Scroll to the new folder.
2167 bookmarksListView.setSelection(0);
2171 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) {
2172 // Get handles for the views from `dialogFragment`.
2173 EditText editBookmarkNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_name_edittext);
2174 EditText editBookmarkUrlEditText = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_url_edittext);
2175 RadioButton currentBookmarkIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_bookmark_current_icon_radiobutton);
2177 // Store the bookmark strings.
2178 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
2179 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
2181 // Update the bookmark.
2182 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
2183 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
2184 } else { // Update the bookmark using the `WebView` favorite icon.
2185 // Create a favorite icon byte array output stream.
2186 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2188 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2189 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
2191 // Convert the favorite icon byte array stream to a byte array.
2192 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
2194 // Update the bookmark and the favorite icon.
2195 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
2198 // Update the bookmarks cursor with the current contents of this folder.
2199 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2201 // Update the list view.
2202 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2206 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
2207 // Get handles for the views from `dialogFragment`.
2208 EditText editFolderNameEditText = dialogFragment.getDialog().findViewById(R.id.edit_folder_name_edittext);
2209 RadioButton currentFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_current_icon_radiobutton);
2210 RadioButton defaultFolderIconRadioButton = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_radiobutton);
2211 ImageView defaultFolderIconImageView = dialogFragment.getDialog().findViewById(R.id.edit_folder_default_icon_imageview);
2213 // Get the new folder name.
2214 String newFolderNameString = editFolderNameEditText.getText().toString();
2216 // Check if the favorite icon has changed.
2217 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
2218 // Update the name in the database.
2219 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2220 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
2221 // Create the new folder icon Bitmap.
2222 Bitmap folderIconBitmap;
2224 // Populate the new folder icon bitmap.
2225 if (defaultFolderIconRadioButton.isChecked()) {
2226 // Get the default folder icon drawable.
2227 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2229 // Convert the folder icon drawable to a bitmap drawable.
2230 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2232 // Convert the folder icon bitmap drawable to a bitmap.
2233 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2234 } else { // Use the `WebView` favorite icon.
2235 // Copy the favorite icon bitmap to the folder icon bitmap.
2236 folderIconBitmap = favoriteIconBitmap;
2239 // Create a folder icon byte array output stream.
2240 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2242 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2243 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2245 // Convert the folder icon byte array stream to a byte array.
2246 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2248 // Update the folder icon in the database.
2249 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2250 } else { // The folder icon and the name have changed.
2251 // Get the new folder icon `Bitmap`.
2252 Bitmap folderIconBitmap;
2253 if (defaultFolderIconRadioButton.isChecked()) {
2254 // Get the default folder icon drawable.
2255 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2257 // Convert the folder icon drawable to a bitmap drawable.
2258 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2260 // Convert the folder icon bitmap drawable to a bitmap.
2261 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2262 } else { // Use the `WebView` favorite icon.
2263 // Copy the favorite icon bitmap to the folder icon bitmap.
2264 folderIconBitmap = favoriteIconBitmap;
2267 // Create a folder icon byte array output stream.
2268 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2270 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2271 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2273 // Convert the folder icon byte array stream to a byte array.
2274 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2276 // Update the folder name and icon in the database.
2277 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2280 // Update the bookmarks cursor with the current contents of this folder.
2281 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2283 // Update the `ListView`.
2284 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2288 public void onCloseDownloadLocationPermissionDialog(int downloadType) {
2289 switch (downloadType) {
2290 case DownloadLocationPermissionDialog.DOWNLOAD_FILE:
2291 // Request the WRITE_EXTERNAL_STORAGE permission with a file request code.
2292 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
2295 case DownloadLocationPermissionDialog.DOWNLOAD_IMAGE:
2296 // Request the WRITE_EXTERNAL_STORAGE permission with an image request code.
2297 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_IMAGE_REQUEST_CODE);
2303 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
2304 // Get a handle for the fragment manager.
2305 FragmentManager fragmentManager = getSupportFragmentManager();
2307 switch (requestCode) {
2308 case DOWNLOAD_FILE_REQUEST_CODE:
2309 // Show the download file alert dialog. When the dialog closes, the correct command will be used based on the permission status.
2310 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, downloadContentDisposition, downloadContentLength);
2312 // On API 23, displaying the fragment must be delayed or the app will crash.
2313 if (Build.VERSION.SDK_INT == 23) {
2314 new Handler().postDelayed(() -> downloadFileDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
2316 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
2319 // Reset the download variables.
2321 downloadContentDisposition = "";
2322 downloadContentLength = 0;
2325 case DOWNLOAD_IMAGE_REQUEST_CODE:
2326 // Show the download image alert dialog. When the dialog closes, the correct command will be used based on the permission status.
2327 DialogFragment downloadImageDialogFragment = DownloadImageDialog.imageUrl(downloadImageUrl);
2329 // On API 23, displaying the fragment must be delayed or the app will crash.
2330 if (Build.VERSION.SDK_INT == 23) {
2331 new Handler().postDelayed(() -> downloadImageDialogFragment.show(fragmentManager, getString(R.string.download)), 500);
2333 downloadImageDialogFragment.show(fragmentManager, getString(R.string.download));
2336 // Reset the image URL variable.
2337 downloadImageUrl = "";
2343 public void onDownloadImage(DialogFragment dialogFragment, String imageUrl) {
2344 // Download the image if it has an HTTP or HTTPS URI.
2345 if (imageUrl.startsWith("http")) {
2346 // Get a handle for the system `DOWNLOAD_SERVICE`.
2347 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
2349 // Parse `imageUrl`.
2350 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
2352 // Get a handle for the cookie manager.
2353 CookieManager cookieManager = CookieManager.getInstance();
2355 // Pass cookies to download manager if cookies are enabled. This is required to download images from websites that require a login.
2356 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
2357 if (cookieManager.acceptCookie()) {
2358 // Get the cookies for `imageUrl`.
2359 String cookies = cookieManager.getCookie(imageUrl);
2361 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
2362 downloadRequest.addRequestHeader("Cookie", cookies);
2365 // Get the file name from the dialog fragment.
2366 EditText downloadImageNameEditText = dialogFragment.getDialog().findViewById(R.id.download_image_name);
2367 String imageName = downloadImageNameEditText.getText().toString();
2369 // Specify the download location.
2370 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
2371 // Download to the public download directory.
2372 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, imageName);
2373 } else { // External write permission denied.
2374 // Download to the app's external download directory.
2375 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, imageName);
2378 // Allow `MediaScanner` to index the download if it is a media file.
2379 downloadRequest.allowScanningByMediaScanner();
2381 // Add the URL as the description for the download.
2382 downloadRequest.setDescription(imageUrl);
2384 // Show the download notification after the download is completed.
2385 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
2387 // Remove the lint warning below that `downloadManager` might be `null`.
2388 assert downloadManager != null;
2390 // Initiate the download.
2391 downloadManager.enqueue(downloadRequest);
2392 } else { // The image is not an HTTP or HTTPS URI.
2393 Snackbar.make(currentWebView, R.string.cannot_download_image, Snackbar.LENGTH_INDEFINITE).show();
2398 public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
2399 // Download the file if it has an HTTP or HTTPS URI.
2400 if (downloadUrl.startsWith("http")) {
2401 // Get a handle for the system `DOWNLOAD_SERVICE`.
2402 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
2404 // Parse `downloadUrl`.
2405 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
2407 // Get a handle for the cookie manager.
2408 CookieManager cookieManager = CookieManager.getInstance();
2410 // Pass cookies to download manager if cookies are enabled. This is required to download files from websites that require a login.
2411 // Code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
2412 if (cookieManager.acceptCookie()) {
2413 // Get the cookies for `downloadUrl`.
2414 String cookies = cookieManager.getCookie(downloadUrl);
2416 // Add the cookies to `downloadRequest`. In the HTTP request header, cookies are named `Cookie`.
2417 downloadRequest.addRequestHeader("Cookie", cookies);
2420 // Get the file name from the dialog fragment.
2421 EditText downloadFileNameEditText = dialogFragment.getDialog().findViewById(R.id.download_file_name);
2422 String fileName = downloadFileNameEditText.getText().toString();
2424 // Specify the download location.
2425 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // External write permission granted.
2426 // Download to the public download directory.
2427 downloadRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
2428 } else { // External write permission denied.
2429 // Download to the app's external download directory.
2430 downloadRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, fileName);
2433 // Allow `MediaScanner` to index the download if it is a media file.
2434 downloadRequest.allowScanningByMediaScanner();
2436 // Add the URL as the description for the download.
2437 downloadRequest.setDescription(downloadUrl);
2439 // Show the download notification after the download is completed.
2440 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
2442 // Remove the lint warning below that `downloadManager` might be `null`.
2443 assert downloadManager != null;
2445 // Initiate the download.
2446 downloadManager.enqueue(downloadRequest);
2447 } else { // The download is not an HTTP or HTTPS URI.
2448 Snackbar.make(currentWebView, R.string.cannot_download_file, Snackbar.LENGTH_INDEFINITE).show();
2452 // Override `onBackPressed` to handle the navigation drawer and and the WebView.
2454 public void onBackPressed() {
2455 // Get a handle for the drawer layout and the tab layout.
2456 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2457 TabLayout tabLayout = findViewById(R.id.tablayout);
2459 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
2460 // Close the navigation drawer.
2461 drawerLayout.closeDrawer(GravityCompat.START);
2462 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
2463 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
2464 // close the bookmarks drawer.
2465 drawerLayout.closeDrawer(GravityCompat.END);
2466 } else { // A subfolder is displayed.
2467 // Place the former parent folder in `currentFolder`.
2468 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
2470 // Load the new folder.
2471 loadBookmarksFolder();
2473 } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history.
2474 // Reset the current domain name so that navigation works if third-party requests are blocked.
2475 currentWebView.resetCurrentDomainName();
2477 // Set navigating history so that the domain settings are applied when the new URL is loaded.
2478 currentWebView.setNavigatingHistory(true);
2481 currentWebView.goBack();
2482 } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs.
2483 // Close the current tab.
2485 } else { // There isn't anything to do in Privacy Browser.
2486 // Run the default commands.
2487 super.onBackPressed();
2489 // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted.
2494 // Process the results of an upload file chooser. Currently there is only one `startActivityForResult` in this activity, so the request code, used to differentiate them, is ignored.
2496 public void onActivityResult(int requestCode, int resultCode, Intent data) {
2497 // File uploads only work on API >= 21.
2498 if (Build.VERSION.SDK_INT >= 21) {
2499 // Pass the file to the WebView.
2500 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
2504 private void loadUrlFromTextBox() {
2505 // Get a handle for the URL edit text.
2506 EditText urlEditText = findViewById(R.id.url_edittext);
2508 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
2509 String unformattedUrlString = urlEditText.getText().toString().trim();
2511 // Initialize the formatted URL string.
2514 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
2515 if (unformattedUrlString.startsWith("content://")) { // This is a Content URL.
2516 // Load the entire content URL.
2517 url = unformattedUrlString;
2518 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2519 unformattedUrlString.startsWith("file://")) { // This is a standard URL.
2520 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
2521 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2522 unformattedUrlString = "https://" + unformattedUrlString;
2525 // Initialize `unformattedUrl`.
2526 URL unformattedUrl = null;
2528 // Convert `unformattedUrlString` to a `URL`, then to a `URI`, and then back to a `String`, which sanitizes the input and adds in any missing components.
2530 unformattedUrl = new URL(unformattedUrlString);
2531 } catch (MalformedURLException e) {
2532 e.printStackTrace();
2535 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2536 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2537 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2538 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2539 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2540 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2543 Uri.Builder uri = new Uri.Builder();
2544 uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2546 // Decode the URI as a UTF-8 string in.
2548 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2549 } catch (UnsupportedEncodingException exception) {
2550 // Do nothing. The formatted URL string will remain blank.
2552 } else if (!unformattedUrlString.isEmpty()){ // This is not a URL, but rather a search string.
2553 // Create an encoded URL String.
2554 String encodedUrlString;
2556 // Sanitize the search input.
2558 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2559 } catch (UnsupportedEncodingException exception) {
2560 encodedUrlString = "";
2563 // Add the base search URL.
2564 url = searchURL + encodedUrlString;
2567 // Clear the focus from the URL edit text. Otherwise, proximate typing in the box will retain the colorized formatting instead of being reset during refocus.
2568 urlEditText.clearFocus();
2574 private void loadUrl(String url) {
2575 // Sanitize the URL.
2576 url = sanitizeUrl(url);
2578 // Apply the domain settings.
2579 applyDomainSettings(currentWebView, url, true, false);
2582 currentWebView.loadUrl(url, customHeaders);
2585 public void findPreviousOnPage(View view) {
2586 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
2587 currentWebView.findNext(false);
2590 public void findNextOnPage(View view) {
2591 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2592 currentWebView.findNext(true);
2595 public void closeFindOnPage(View view) {
2596 // Get a handle for the views.
2597 Toolbar toolbar = findViewById(R.id.toolbar);
2598 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2599 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2601 // Delete the contents of `find_on_page_edittext`.
2602 findOnPageEditText.setText(null);
2604 // Clear the highlighted phrases if the WebView is not null.
2605 if (currentWebView != null) {
2606 currentWebView.clearMatches();
2609 // Hide the find on page linear layout.
2610 findOnPageLinearLayout.setVisibility(View.GONE);
2612 // Show the toolbar.
2613 toolbar.setVisibility(View.VISIBLE);
2615 // Get a handle for the input method manager.
2616 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2618 // Remove the lint warning below that the input method manager might be null.
2619 assert inputMethodManager != null;
2621 // Hide the keyboard.
2622 inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2625 private void applyAppSettings() {
2626 // Initialize the app if this is the first run. This is done here instead of in `onCreate()` to shorten the time that an unthemed background is displayed on app startup.
2627 if (webViewDefaultUserAgent == null) {
2631 // Get a handle for the shared preferences.
2632 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2634 // Store the values from the shared preferences in variables.
2635 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
2636 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
2637 sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
2638 sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
2639 sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
2640 proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
2641 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
2642 hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
2643 scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
2645 // Get handles for the views that need to be modified.
2646 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
2647 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
2648 ActionBar actionBar = getSupportActionBar();
2649 Toolbar toolbar = findViewById(R.id.toolbar);
2650 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2651 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
2652 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2654 // Remove the incorrect lint warning below that the action bar might be null.
2655 assert actionBar != null;
2657 // Apply the proxy through Orbot settings.
2658 applyProxyThroughOrbot(false);
2660 // Set Do Not Track status.
2661 if (doNotTrackEnabled) {
2662 customHeaders.put("DNT", "1");
2664 customHeaders.remove("DNT");
2667 // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
2668 CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
2669 AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
2670 AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
2671 AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
2673 // Add the scrolling behavior to the layout parameters.
2675 // Enable scrolling of the app bar.
2676 swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
2677 toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
2678 findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
2679 tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
2681 // Disable scrolling of the app bar.
2682 swipeRefreshLayoutParams.setBehavior(null);
2683 toolbarLayoutParams.setScrollFlags(0);
2684 findOnPageLayoutParams.setScrollFlags(0);
2685 tabsLayoutParams.setScrollFlags(0);
2687 // Expand the app bar if it is currently collapsed.
2688 appBarLayout.setExpanded(true);
2691 // Apply the modified layout parameters.
2692 swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
2693 toolbar.setLayoutParams(toolbarLayoutParams);
2694 findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
2695 tabsLinearLayout.setLayoutParams(tabsLayoutParams);
2697 // Set the app bar scrolling for each WebView.
2698 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
2699 // Get the WebView tab fragment.
2700 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
2702 // Get the fragment view.
2703 View fragmentView = webViewTabFragment.getView();
2705 // Only modify the WebViews if they exist.
2706 if (fragmentView != null) {
2707 // Get the nested scroll WebView from the tab fragment.
2708 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
2710 // Set the app bar scrolling.
2711 nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
2715 // Update the full screen browsing mode settings.
2716 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
2717 // Update the visibility of the app bar, which might have changed in the settings.
2719 // Hide the tab linear layout.
2720 tabsLinearLayout.setVisibility(View.GONE);
2722 // Hide the action bar.
2725 // Show the tab linear layout.
2726 tabsLinearLayout.setVisibility(View.VISIBLE);
2728 // Show the action bar.
2732 // Hide the banner ad in the free flavor.
2733 if (BuildConfig.FLAVOR.contentEquals("free")) {
2734 AdHelper.hideAd(findViewById(R.id.adview));
2737 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
2738 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2740 /* Hide the system bars.
2741 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2742 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
2743 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2744 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2746 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
2747 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2748 } else { // Privacy Browser is not in full screen browsing mode.
2749 // 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.
2750 inFullScreenBrowsingMode = false;
2752 // Show the tab linear layout.
2753 tabsLinearLayout.setVisibility(View.VISIBLE);
2755 // Show the action bar.
2758 // Show the banner ad in the free flavor.
2759 if (BuildConfig.FLAVOR.contentEquals("free")) {
2760 // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead.
2761 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
2764 // Remove the `SYSTEM_UI` flags from the root frame layout.
2765 rootFrameLayout.setSystemUiVisibility(0);
2767 // Add the translucent status flag.
2768 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2772 private void initializeApp() {
2773 // Get a handle for the shared preferences.
2774 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2776 // Get the theme preference.
2777 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
2779 // Get a handle for the input method.
2780 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2782 // Remove the lint warning below that the input method manager might be null.
2783 assert inputMethodManager != null;
2785 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
2786 redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
2787 initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
2788 finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
2790 // Get handles for the URL views.
2791 EditText urlEditText = findViewById(R.id.url_edittext);
2793 // Remove the formatting from the URL edit text when the user is editing the text.
2794 urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
2795 if (hasFocus) { // The user is editing the URL text box.
2796 // Remove the highlighting.
2797 urlEditText.getText().removeSpan(redColorSpan);
2798 urlEditText.getText().removeSpan(initialGrayColorSpan);
2799 urlEditText.getText().removeSpan(finalGrayColorSpan);
2800 } else { // The user has stopped editing the URL text box.
2801 // Move to the beginning of the string.
2802 urlEditText.setSelection(0);
2804 // Reapply the highlighting.
2809 // Set the go button on the keyboard to load the URL in `urlTextBox`.
2810 urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
2811 // If the event is a key-down event on the `enter` button, load the URL.
2812 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
2813 // Load the URL into the mainWebView and consume the event.
2814 loadUrlFromTextBox();
2816 // If the enter key was pressed, consume the event.
2819 // If any other key was pressed, do not consume the event.
2824 // Initialize the Orbot status and the waiting for Orbot trackers.
2825 orbotStatus = "unknown";
2826 waitingForOrbot = false;
2828 // Create an Orbot status `BroadcastReceiver`.
2829 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
2831 public void onReceive(Context context, Intent intent) {
2832 // Store the content of the status message in `orbotStatus`.
2833 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
2835 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
2836 if (orbotStatus.equals("ON") && waitingForOrbot) {
2837 // Reset the waiting for Orbot status.
2838 waitingForOrbot = false;
2840 // Get the intent that started the app.
2841 Intent launchingIntent = getIntent();
2843 // Get the information from the intent.
2844 String launchingIntentAction = launchingIntent.getAction();
2845 Uri launchingIntentUriData = launchingIntent.getData();
2847 // If the intent action is a web search, perform the search.
2848 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
2849 // Create an encoded URL string.
2850 String encodedUrlString;
2852 // Sanitize the search input and convert it to a search.
2854 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
2855 } catch (UnsupportedEncodingException exception) {
2856 encodedUrlString = "";
2859 // Load the completed search URL.
2860 loadUrl(searchURL + encodedUrlString);
2861 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
2862 // Load the URL from the intent.
2863 loadUrl(launchingIntentUriData.toString());
2864 } else { // The is no URL in the intent.
2865 // Select the homepage based on the proxy through Orbot status.
2866 if (proxyThroughOrbot) {
2867 // Load the Tor homepage.
2868 loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
2870 // Load the normal homepage.
2871 loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
2878 // Register `orbotStatusBroadcastReceiver` on `this` context.
2879 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
2881 // Get handles for views that need to be modified.
2882 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2883 NavigationView navigationView = findViewById(R.id.navigationview);
2884 TabLayout tabLayout = findViewById(R.id.tablayout);
2885 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
2886 ViewPager webViewPager = findViewById(R.id.webviewpager);
2887 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2888 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
2889 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
2890 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
2891 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2893 // Listen for touches on the navigation menu.
2894 navigationView.setNavigationItemSelectedListener(this);
2896 // Get handles for the navigation menu and the back and forward menu items. The menu is zero-based.
2897 Menu navigationMenu = navigationView.getMenu();
2898 MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
2899 MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
2900 MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
2901 MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5);
2903 // Update the web view pager every time a tab is modified.
2904 webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
2906 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
2911 public void onPageSelected(int position) {
2912 // Close the find on page bar if it is open.
2913 closeFindOnPage(null);
2915 // Set the current WebView.
2916 setCurrentWebView(position);
2918 // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled via swiping in the view pager or by creating a new tab.
2919 if (tabLayout.getSelectedTabPosition() != position) {
2920 // Create a handler to select the tab.
2921 Handler selectTabHandler = new Handler();
2923 // Create a runnable to select the tab.
2924 Runnable selectTabRunnable = () -> {
2925 // Get a handle for the tab.
2926 TabLayout.Tab tab = tabLayout.getTabAt(position);
2928 // Assert that the tab is not null.
2935 // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.
2936 selectTabHandler.postDelayed(selectTabRunnable, 150);
2941 public void onPageScrollStateChanged(int state) {
2946 // Display the View SSL Certificate dialog when the currently selected tab is reselected.
2947 tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
2949 public void onTabSelected(TabLayout.Tab tab) {
2950 // Select the same page in the view pager.
2951 webViewPager.setCurrentItem(tab.getPosition());
2955 public void onTabUnselected(TabLayout.Tab tab) {
2960 public void onTabReselected(TabLayout.Tab tab) {
2961 // Instantiate the View SSL Certificate dialog.
2962 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
2964 // Display the View SSL Certificate dialog.
2965 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
2969 // Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
2970 // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
2972 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
2973 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
2974 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
2975 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
2977 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
2978 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
2979 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
2980 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
2983 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
2984 launchBookmarksActivityFab.setOnClickListener(v -> {
2985 // Get a copy of the favorite icon bitmap.
2986 Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
2988 // Create a favorite icon byte array output stream.
2989 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2991 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2992 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2994 // Convert the favorite icon byte array stream to a byte array.
2995 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2997 // Create an intent to launch the bookmarks activity.
2998 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
3000 // Add the extra information to the intent.
3001 bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
3002 bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
3003 bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
3004 bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
3007 startActivity(bookmarksIntent);
3010 // Set the create new bookmark folder FAB to display an alert dialog.
3011 createBookmarkFolderFab.setOnClickListener(v -> {
3012 // Create a create bookmark folder dialog.
3013 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
3015 // Show the create bookmark folder dialog.
3016 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
3019 // Set the create new bookmark FAB to display an alert dialog.
3020 createBookmarkFab.setOnClickListener(view -> {
3021 // Instantiate the create bookmark dialog.
3022 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
3024 // Display the create bookmark dialog.
3025 createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
3028 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
3029 findOnPageEditText.addTextChangedListener(new TextWatcher() {
3031 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3036 public void onTextChanged(CharSequence s, int start, int before, int count) {
3041 public void afterTextChanged(Editable s) {
3042 // Search for the text in the WebView if it is not null. Sometimes on resume after a period of non-use the WebView will be null.
3043 if (currentWebView != null) {
3044 currentWebView.findAllAsync(findOnPageEditText.getText().toString());
3049 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
3050 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
3051 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
3052 // Hide the soft keyboard.
3053 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3055 // Consume the event.
3057 } else { // A different key was pressed.
3058 // Do not consume the event.
3063 // Implement swipe to refresh.
3064 swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
3066 // Store the default progress view offsets for use later in `initializeWebView()`.
3067 defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
3068 defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
3070 // Set the swipe to refresh color according to the theme.
3072 swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
3073 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
3075 swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
3078 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
3079 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
3080 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
3082 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
3083 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
3085 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
3086 currentBookmarksFolder = "";
3088 // Load the home folder, which is `""` in the database.
3089 loadBookmarksFolder();
3091 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
3092 // Convert the id from long to int to match the format of the bookmarks database.
3093 int databaseID = (int) id;
3095 // Get the bookmark cursor for this ID and move it to the first row.
3096 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
3097 bookmarkCursor.moveToFirst();
3099 // Act upon the bookmark according to the type.
3100 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
3101 // Store the new folder name in `currentBookmarksFolder`.
3102 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3104 // Load the new folder.
3105 loadBookmarksFolder();
3106 } else { // The selected bookmark is not a folder.
3107 // Load the bookmark URL.
3108 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
3110 // Close the bookmarks drawer.
3111 drawerLayout.closeDrawer(GravityCompat.END);
3114 // Close the `Cursor`.
3115 bookmarkCursor.close();
3118 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
3119 // Convert the database ID from `long` to `int`.
3120 int databaseId = (int) id;
3122 // Find out if the selected bookmark is a folder.
3123 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
3126 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
3127 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3129 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
3130 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
3131 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
3133 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
3134 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
3135 editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark));
3138 // Consume the event.
3142 // Get the status bar pixel size.
3143 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3144 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3146 // Get the resource density.
3147 float screenDensity = getResources().getDisplayMetrics().density;
3149 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
3150 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3151 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3152 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3154 // The drawer listener is used to update the navigation menu.`
3155 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
3157 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
3161 public void onDrawerOpened(@NonNull View drawerView) {
3165 public void onDrawerClosed(@NonNull View drawerView) {
3169 public void onDrawerStateChanged(int newState) {
3170 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
3171 // Get handles for the drawer headers.
3172 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
3173 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
3175 // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
3176 if (navigationHeaderTextView != null) {
3177 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
3180 // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
3181 if (bookmarksHeaderTextView != null) {
3182 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
3185 // Update the navigation menu items if the WebView is not null.
3186 if (currentWebView != null) {
3187 navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
3188 navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
3189 navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
3190 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
3192 // Hide the keyboard (if displayed).
3193 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3196 // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers.
3197 urlEditText.clearFocus();
3198 currentWebView.clearFocus();
3203 // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
3204 customHeaders.put("X-Requested-With", "");
3206 // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen.
3207 @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
3209 // Get a handle for the WebView.
3210 WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
3212 // Store the default user agent.
3213 webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
3215 // Destroy the bare WebView.
3216 bareWebView.destroy();
3219 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
3220 @SuppressLint("SetJavaScriptEnabled")
3221 private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
3222 // Store a copy of the current user agent to track changes for the return boolean.
3223 String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
3225 // Parse the URL into a URI.
3226 Uri uri = Uri.parse(url);
3228 // Extract the domain from `uri`.
3229 String newHostName = uri.getHost();
3231 // Strings don't like to be null.
3232 if (newHostName == null) {
3236 // Apply the domain settings if a new domain is being loaded or if the new domain is blank. This allows the user to set temporary settings for JavaScript, cookies, DOM storage, etc.
3237 if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) {
3238 // Set the new host name as the current domain name.
3239 nestedScrollWebView.setCurrentDomainName(newHostName);
3241 // Reset the ignoring of pinned domain information.
3242 nestedScrollWebView.setIgnorePinnedDomainInformation(false);
3244 // Clear any pinned SSL certificate or IP addresses.
3245 nestedScrollWebView.clearPinnedSslCertificate();
3246 nestedScrollWebView.clearPinnedIpAddresses();
3248 // Reset the favorite icon if specified.
3250 // Initialize the favorite icon.
3251 nestedScrollWebView.initializeFavoriteIcon();
3253 // Get the current page position.
3254 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
3256 // Get a handle for the tab layout.
3257 TabLayout tabLayout = findViewById(R.id.tablayout);
3259 // Get the corresponding tab.
3260 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
3262 // Update the tab if it isn't null, which sometimes happens when restarting from the background.
3264 // Get the tab custom view.
3265 View tabCustomView = tab.getCustomView();
3267 // Remove the warning below that the tab custom view might be null.
3268 assert tabCustomView != null;
3270 // Get the tab views.
3271 ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
3272 TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
3274 // Set the default favorite icon as the favorite icon for this tab.
3275 tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
3277 // Set the loading title text.
3278 tabTitleTextView.setText(R.string.loading);
3282 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
3283 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
3285 // Get a full cursor from `domainsDatabaseHelper`.
3286 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
3288 // Initialize `domainSettingsSet`.
3289 Set<String> domainSettingsSet = new HashSet<>();
3291 // Get the domain name column index.
3292 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
3294 // Populate `domainSettingsSet`.
3295 for (int i = 0; i < domainNameCursor.getCount(); i++) {
3296 // Move `domainsCursor` to the current row.
3297 domainNameCursor.moveToPosition(i);
3299 // Store the domain name in `domainSettingsSet`.
3300 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
3303 // Close `domainNameCursor.
3304 domainNameCursor.close();
3306 // Initialize the domain name in database variable.
3307 String domainNameInDatabase = null;
3309 // Check the hostname against the domain settings set.
3310 if (domainSettingsSet.contains(newHostName)) { // The hostname is contained in the domain settings set.
3311 // Record the domain name in the database.
3312 domainNameInDatabase = newHostName;
3314 // Set the domain settings applied tracker to true.
3315 nestedScrollWebView.setDomainSettingsApplied(true);
3316 } else { // The hostname is not contained in the domain settings set.
3317 // Set the domain settings applied tracker to false.
3318 nestedScrollWebView.setDomainSettingsApplied(false);
3321 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
3322 while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
3323 if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`.
3324 // Set the domain settings applied tracker to true.
3325 nestedScrollWebView.setDomainSettingsApplied(true);
3327 // Store the applied domain names as it appears in the database.
3328 domainNameInDatabase = "*." + newHostName;
3331 // Strip out the lowest subdomain of of the host name.
3332 newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
3336 // Get a handle for the shared preferences.
3337 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3339 // Store the general preference information.
3340 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
3341 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
3342 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
3343 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
3344 boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
3345 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
3347 // Get a handle for the cookie manager.
3348 CookieManager cookieManager = CookieManager.getInstance();
3350 // Get handles for the views.
3351 RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
3352 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
3354 // Initialize the user agent array adapter and string array.
3355 ArrayAdapter<CharSequence> userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
3356 String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
3358 if (nestedScrollWebView.getDomainSettingsApplied()) { // The url has custom domain settings.
3359 // Get a cursor for the current host and move it to the first position.
3360 Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
3361 currentDomainSettingsCursor.moveToFirst();
3363 // Get the settings from the cursor.
3364 nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
3365 nestedScrollWebView.setDomainSettingsJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
3366 nestedScrollWebView.setAcceptFirstPartyCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
3367 boolean domainThirdPartyCookiesEnabled = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
3368 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
3369 // Form data can be removed once the minimum API >= 26.
3370 boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
3371 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST,
3372 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
3373 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY,
3374 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
3375 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
3376 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
3377 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
3378 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
3379 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY,
3380 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
3381 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
3382 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
3383 String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
3384 int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
3385 int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
3386 int nightModeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
3387 int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT));
3388 int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
3389 boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
3390 String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
3391 String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
3392 String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
3393 String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
3394 String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
3395 String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
3396 boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
3397 String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
3399 // Create the pinned SSL date variables.
3400 Date pinnedSslStartDate;
3401 Date pinnedSslEndDate;
3403 // Set the pinned SSL certificate start date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0.
3404 if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
3405 pinnedSslStartDate = null;
3407 pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
3410 // Set the pinned SSL certificate end date to `null` if the saved date `long` is 0 because creating a new Date results in an error if the input is 0.
3411 if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
3412 pinnedSslEndDate = null;
3414 pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
3417 // If there is a pinned SSL certificate, store it in the WebView.
3418 if (pinnedSslCertificate) {
3419 nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
3420 pinnedSslStartDate, pinnedSslEndDate);
3423 // If there is a pinned IP address, store it in the WebView.
3424 if (pinnedIpAddresses) {
3425 nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses);
3428 // Set night mode according to the night mode int.
3429 switch (nightModeInt) {
3430 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
3431 // Set night mode according to the current default.
3432 nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
3435 case DomainsDatabaseHelper.ENABLED:
3436 // Enable night mode.
3437 nestedScrollWebView.setNightMode(true);
3440 case DomainsDatabaseHelper.DISABLED:
3441 // Disable night mode.
3442 nestedScrollWebView.setNightMode(false);
3446 // Enable JavaScript if night mode is enabled.
3447 if (nestedScrollWebView.getNightMode()) {
3448 // Enable JavaScript.
3449 nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
3451 // Set JavaScript according to the domain settings.
3452 nestedScrollWebView.getSettings().setJavaScriptEnabled(nestedScrollWebView.getDomainSettingsJavaScriptEnabled());
3455 // Close the current host domain settings cursor.
3456 currentDomainSettingsCursor.close();
3458 // Apply the domain settings.
3459 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
3461 // Set third-party cookies status if API >= 21.
3462 if (Build.VERSION.SDK_INT >= 21) {
3463 cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, domainThirdPartyCookiesEnabled);
3466 // Apply the form data setting if the API < 26.
3467 if (Build.VERSION.SDK_INT < 26) {
3468 nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
3471 // Apply the font size.
3472 if (fontSize == 0) { // Apply the default font size.
3473 nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
3474 } else { // Apply the specified font size.
3475 nestedScrollWebView.getSettings().setTextZoom(fontSize);
3478 // Set the user agent.
3479 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
3480 // Get the array position of the default user agent name.
3481 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
3483 // Set the user agent according to the system default.
3484 switch (defaultUserAgentArrayPosition) {
3485 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
3486 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
3487 nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
3490 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
3491 // Set the user agent to `""`, which uses the default value.
3492 nestedScrollWebView.getSettings().setUserAgentString("");
3495 case SETTINGS_CUSTOM_USER_AGENT:
3496 // Set the default custom user agent.
3497 nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
3501 // Get the user agent string from the user agent data array
3502 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
3504 } else { // Set the user agent according to the stored name.
3505 // Get the array position of the user agent name.
3506 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
3508 switch (userAgentArrayPosition) {
3509 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
3510 nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
3513 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
3514 // Set the user agent to `""`, which uses the default value.
3515 nestedScrollWebView.getSettings().setUserAgentString("");
3519 // Get the user agent string from the user agent data array.
3520 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
3524 // Set swipe to refresh.
3525 switch (swipeToRefreshInt) {
3526 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
3527 // Store the swipe to refresh status in the nested scroll WebView.
3528 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
3530 // Apply swipe to refresh according to the default. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
3531 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
3534 case DomainsDatabaseHelper.ENABLED:
3535 // Store the swipe to refresh status in the nested scroll WebView.
3536 nestedScrollWebView.setSwipeToRefresh(true);
3538 // Enable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
3539 swipeRefreshLayout.setEnabled(true);
3542 case DomainsDatabaseHelper.DISABLED:
3543 // Store the swipe to refresh status in the nested scroll WebView.
3544 nestedScrollWebView.setSwipeToRefresh(false);
3546 // Disable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
3547 swipeRefreshLayout.setEnabled(false);
3550 // Set the viewport.
3551 switch (wideViewportInt) {
3552 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
3553 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
3556 case DomainsDatabaseHelper.ENABLED:
3557 nestedScrollWebView.getSettings().setUseWideViewPort(true);
3560 case DomainsDatabaseHelper.DISABLED:
3561 nestedScrollWebView.getSettings().setUseWideViewPort(false);
3565 // Set the loading of webpage images.
3566 switch (displayWebpageImagesInt) {
3567 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
3568 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
3571 case DomainsDatabaseHelper.ENABLED:
3572 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true);
3575 case DomainsDatabaseHelper.DISABLED:
3576 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false);
3580 // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
3582 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
3584 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
3586 } else { // The new URL does not have custom domain settings. Load the defaults.
3587 // Store the values from the shared preferences.
3588 boolean defaultJavaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
3589 nestedScrollWebView.setAcceptFirstPartyCookies(sharedPreferences.getBoolean("first_party_cookies", false));
3590 boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
3591 nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
3592 boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
3593 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, sharedPreferences.getBoolean("easylist", true));
3594 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
3595 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
3596 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
3597 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
3598 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
3599 nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
3601 // Enable JavaScript if night mode is enabled.
3602 if (nestedScrollWebView.getNightMode()) {
3603 // Enable JavaScript.
3604 nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
3606 // Set JavaScript according to the domain settings.
3607 nestedScrollWebView.getSettings().setJavaScriptEnabled(defaultJavaScriptEnabled);
3610 // Apply the default settings.
3611 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
3612 nestedScrollWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
3614 // Apply the form data setting if the API < 26.
3615 if (Build.VERSION.SDK_INT < 26) {
3616 nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
3619 // Store the swipe to refresh status in the nested scroll WebView.
3620 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
3622 // Apply swipe to refresh according to the default.
3623 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
3625 // Reset the pinned variables.
3626 nestedScrollWebView.setDomainSettingsDatabaseId(-1);
3628 // Set third-party cookies status if API >= 21.
3629 if (Build.VERSION.SDK_INT >= 21) {
3630 cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled);
3633 // Get the array position of the user agent name.
3634 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
3636 // Set the user agent.
3637 switch (userAgentArrayPosition) {
3638 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
3639 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
3640 nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
3643 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
3644 // Set the user agent to `""`, which uses the default value.
3645 nestedScrollWebView.getSettings().setUserAgentString("");
3648 case SETTINGS_CUSTOM_USER_AGENT:
3649 // Set the default custom user agent.
3650 nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
3654 // Get the user agent string from the user agent data array
3655 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
3658 // Set the viewport.
3659 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
3661 // Set the loading of webpage images.
3662 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
3664 // Set a transparent background on URL edit text. The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21.
3665 urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
3668 // Close the domains database helper.
3669 domainsDatabaseHelper.close();
3671 // Update the privacy icons.
3672 updatePrivacyIcons(true);
3675 // Reload the website if returning from the Domains activity.
3676 if (reloadWebsite) {
3677 nestedScrollWebView.reload();
3680 // Return the user agent changed status.
3681 return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent);
3684 private void applyProxyThroughOrbot(boolean reloadWebsite) {
3685 // Get a handle for the shared preferences.
3686 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3688 // Get the search and theme preferences.
3689 String torSearchString = sharedPreferences.getString("tor_search", getString(R.string.tor_search_default_value));
3690 String torSearchCustomUrlString = sharedPreferences.getString("tor_search_custom_url", getString(R.string.tor_search_custom_url_default_value));
3691 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
3692 String searchCustomUrlString = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
3693 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
3695 // Get a handle for the app bar layout.
3696 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
3698 // Set the homepage, search, and proxy options.
3699 if (proxyThroughOrbot) { // Set the Tor options.
3700 // Set the search URL.
3701 if (torSearchString.equals("Custom URL")) { // Get the custom URL string.
3702 searchURL = torSearchCustomUrlString;
3703 } else { // Use the string from the pre-built list.
3704 searchURL = torSearchString;
3707 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
3708 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
3710 // Set the app bar background to indicate proxying through Orbot is enabled.
3712 appBarLayout.setBackgroundResource(R.color.dark_blue_30);
3714 appBarLayout.setBackgroundResource(R.color.blue_50);
3717 // Check to see if Orbot is ready.
3718 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
3719 // Set `waitingForOrbot`.
3720 waitingForOrbot = true;
3722 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
3723 currentWebView.getSettings().setUseWideViewPort(false);
3725 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
3726 currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
3727 } else if (reloadWebsite) { // Orbot is ready and the website should be reloaded.
3728 // Reload the website.
3729 currentWebView.reload();
3731 } else { // Set the non-Tor options.
3732 // Set the search URL.
3733 if (searchString.equals("Custom URL")) { // Get the custom URL string.
3734 searchURL = searchCustomUrlString;
3735 } else { // Use the string from the pre-built list.
3736 searchURL = searchString;
3739 // Reset the proxy to default. The host is `""` and the port is `"0"`.
3740 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
3742 // Set the default app bar layout background.
3744 appBarLayout.setBackgroundResource(R.color.gray_900);
3746 appBarLayout.setBackgroundResource(R.color.gray_100);
3749 // Reset `waitingForOrbot.
3750 waitingForOrbot = false;
3752 // Reload the WebViews if requested.
3753 if (reloadWebsite) {
3754 // Reload the WebViews.
3755 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3756 // Get the WebView tab fragment.
3757 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3759 // Get the fragment view.
3760 View fragmentView = webViewTabFragment.getView();
3762 // Only reload the WebViews if they exist.
3763 if (fragmentView != null) {
3764 // Get the nested scroll WebView from the tab fragment.
3765 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3767 // Reload the WebView.
3768 nestedScrollWebView.reload();
3775 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
3776 // Only update the privacy icons if the options menu and the current WebView have already been populated.
3777 if ((optionsMenu != null) && (currentWebView != null)) {
3778 // Get a handle for the shared preferences.
3779 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3781 // Get the theme and screenshot preferences.
3782 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
3784 // Get handles for the menu items.
3785 MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript);
3786 MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies);
3787 MenuItem domStorageMenuItem = optionsMenu.findItem(R.id.toggle_dom_storage);
3788 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
3790 // Update the privacy icon.
3791 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled.
3792 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
3793 } else if (currentWebView.getAcceptFirstPartyCookies()) { // JavaScript is disabled but cookies are enabled.
3794 privacyMenuItem.setIcon(R.drawable.warning);
3795 } else { // All the dangerous features are disabled.
3796 privacyMenuItem.setIcon(R.drawable.privacy_mode);
3799 // Update the first-party cookies icon.
3800 if (currentWebView.getAcceptFirstPartyCookies()) { // First-party cookies are enabled.
3801 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
3802 } else { // First-party cookies are disabled.
3804 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
3806 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
3810 // Update the DOM storage icon.
3811 if (currentWebView.getSettings().getJavaScriptEnabled() && currentWebView.getSettings().getDomStorageEnabled()) { // Both JavaScript and DOM storage are enabled.
3812 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
3813 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled but DOM storage is disabled.
3815 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
3817 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
3819 } else { // JavaScript is disabled, so DOM storage is ghosted.
3821 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
3823 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
3827 // Update the refresh icon.
3829 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
3831 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
3834 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
3835 if (runInvalidateOptionsMenu) {
3836 invalidateOptionsMenu();
3841 private void openUrlWithExternalApp(String url) {
3842 // Create a download intent. Not specifying the action type will display the maximum number of options.
3843 Intent downloadIntent = new Intent();
3845 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
3846 downloadIntent.setDataAndType(Uri.parse(url), "text/html");
3848 // Flag the intent to open in a new task.
3849 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3851 // Show the chooser.
3852 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
3855 private void highlightUrlText() {
3856 // Get a handle for the URL edit text.
3857 EditText urlEditText = findViewById(R.id.url_edittext);
3859 // Only highlight the URL text if the box is not currently selected.
3860 if (!urlEditText.hasFocus()) {
3861 // Get the URL string.
3862 String urlString = urlEditText.getText().toString();
3864 // Highlight the URL according to the protocol.
3865 if (urlString.startsWith("file://")) { // This is a file URL.
3866 // De-emphasize only the protocol.
3867 urlEditText.getText().setSpan(initialGrayColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3868 } else if (urlString.startsWith("content://")) {
3869 // De-emphasize only the protocol.
3870 urlEditText.getText().setSpan(initialGrayColorSpan, 0, 10, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3871 } else { // This is a web URL.
3872 // Get the index of the `/` immediately after the domain name.
3873 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
3875 // Create a base URL string.
3878 // Get the base URL.
3879 if (endOfDomainName > 0) { // There is at least one character after the base URL.
3880 // Get the base URL.
3881 baseUrl = urlString.substring(0, endOfDomainName);
3882 } else { // There are no characters after the base URL.
3883 // Set the base URL to be the entire URL string.
3884 baseUrl = urlString;
3887 // Get the index of the last `.` in the domain.
3888 int lastDotIndex = baseUrl.lastIndexOf(".");
3890 // Get the index of the penultimate `.` in the domain.
3891 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
3893 // Markup the beginning of the URL.
3894 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
3895 urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3897 // De-emphasize subdomains.
3898 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
3899 urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3901 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
3902 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
3903 // De-emphasize the protocol and the additional subdomains.
3904 urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3905 } else { // There is only one subdomain in the domain name.
3906 // De-emphasize only the protocol.
3907 urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3911 // De-emphasize the text after the domain name.
3912 if (endOfDomainName > 0) {
3913 urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
3919 private void loadBookmarksFolder() {
3920 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
3921 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
3923 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
3924 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
3926 public View newView(Context context, Cursor cursor, ViewGroup parent) {
3927 // Inflate the individual item layout. `false` does not attach it to the root.
3928 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
3932 public void bindView(View view, Context context, Cursor cursor) {
3933 // Get handles for the views.
3934 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
3935 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
3937 // Get the favorite icon byte array from the cursor.
3938 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
3940 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
3941 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
3943 // Display the bitmap in `bookmarkFavoriteIcon`.
3944 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
3946 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
3947 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3948 bookmarkNameTextView.setText(bookmarkNameString);
3950 // Make the font bold for folders.
3951 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
3952 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
3953 } else { // Reset the font to default for normal bookmarks.
3954 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
3959 // Get a handle for the bookmarks list view.
3960 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
3962 // Populate the list view with the adapter.
3963 bookmarksListView.setAdapter(bookmarksCursorAdapter);
3965 // Get a handle for the bookmarks title text view.
3966 TextView bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
3968 // Set the bookmarks drawer title.
3969 if (currentBookmarksFolder.isEmpty()) {
3970 bookmarksTitleTextView.setText(R.string.bookmarks);
3972 bookmarksTitleTextView.setText(currentBookmarksFolder);
3976 private void openWithApp(String url) {
3977 // Create the open with intent with `ACTION_VIEW`.
3978 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
3980 // Set the URI but not the MIME type. This should open all available apps.
3981 openWithAppIntent.setData(Uri.parse(url));
3983 // Flag the intent to open in a new task.
3984 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3986 // Show the chooser.
3987 startActivity(openWithAppIntent);
3990 private void openWithBrowser(String url) {
3991 // Create the open with intent with `ACTION_VIEW`.
3992 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
3994 // Set the URI and the MIME type. `"text/html"` should load browser options.
3995 openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
3997 // Flag the intent to open in a new task.
3998 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4000 // Show the chooser.
4001 startActivity(openWithBrowserIntent);
4004 private String sanitizeUrl(String url) {
4005 // Sanitize Google Analytics.
4006 if (sanitizeGoogleAnalytics) {
4008 if (url.contains("?utm_")) {
4009 url = url.substring(0, url.indexOf("?utm_"));
4013 if (url.contains("&utm_")) {
4014 url = url.substring(0, url.indexOf("&utm_"));
4018 // Sanitize Facebook Click IDs.
4019 if (sanitizeFacebookClickIds) {
4020 // Remove `?fbclid=`.
4021 if (url.contains("?fbclid=")) {
4022 url = url.substring(0, url.indexOf("?fbclid="));
4025 // Remove `&fbclid=`.
4026 if (url.contains("&fbclid=")) {
4027 url = url.substring(0, url.indexOf("&fbclid="));
4031 // Sanitize Twitter AMP redirects.
4032 if (sanitizeTwitterAmpRedirects) {
4034 if (url.contains("?amp=1")) {
4035 url = url.substring(0, url.indexOf("?amp=1"));
4039 // Return the sanitized URL.
4043 public void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
4044 // Store the blocklists.
4045 easyList = combinedBlocklists.get(0);
4046 easyPrivacy = combinedBlocklists.get(1);
4047 fanboysAnnoyanceList = combinedBlocklists.get(2);
4048 fanboysSocialList = combinedBlocklists.get(3);
4049 ultraPrivacy = combinedBlocklists.get(4);
4051 // Add the first tab.
4055 public void addTab(View view) {
4056 // Add a new tab with a blank URL.
4060 private void addNewTab(String url) {
4061 // Sanitize the URL.
4062 url = sanitizeUrl(url);
4064 // Get a handle for the tab layout and the view pager.
4065 TabLayout tabLayout = findViewById(R.id.tablayout);
4066 ViewPager webViewPager = findViewById(R.id.webviewpager);
4068 // Get the new page number. The page numbers are 0 indexed, so the new page number will match the current count.
4069 int newTabNumber = tabLayout.getTabCount();
4072 tabLayout.addTab(tabLayout.newTab());
4075 TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber);
4077 // Remove the lint warning below that the current tab might be null.
4078 assert newTab != null;
4080 // Set a custom view on the new tab.
4081 newTab.setCustomView(R.layout.tab_custom_view);
4083 // Add the new WebView page.
4084 webViewPagerAdapter.addPage(newTabNumber, webViewPager, url);
4087 public void closeTab(View view) {
4088 // Get a handle for the tab layout.
4089 TabLayout tabLayout = findViewById(R.id.tablayout);
4091 // Run the command according to the number of tabs.
4092 if (tabLayout.getTabCount() > 1) { // There is more than one tab open.
4093 // Close the current tab.
4095 } else { // There is only one tab open.
4100 private void closeCurrentTab() {
4101 // Get handles for the views.
4102 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
4103 TabLayout tabLayout = findViewById(R.id.tablayout);
4104 ViewPager webViewPager = findViewById(R.id.webviewpager);
4106 // Get the current tab number.
4107 int currentTabNumber = tabLayout.getSelectedTabPosition();
4109 // Delete the current tab.
4110 tabLayout.removeTabAt(currentTabNumber);
4112 // Delete the current page. If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset.
4113 if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
4114 setCurrentWebView(currentTabNumber);
4117 // Expand the app bar if it is currently collapsed.
4118 appBarLayout.setExpanded(true);
4121 private void clearAndExit() {
4122 // Get a handle for the shared preferences.
4123 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4125 // Close the bookmarks cursor and database.
4126 bookmarksCursor.close();
4127 bookmarksDatabaseHelper.close();
4129 // Get the status of the clear everything preference.
4130 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
4132 // Get a handle for the runtime.
4133 Runtime runtime = Runtime.getRuntime();
4135 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
4136 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
4137 String privateDataDirectoryString = getApplicationInfo().dataDir;
4140 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
4141 // The command to remove cookies changed slightly in API 21.
4142 if (Build.VERSION.SDK_INT >= 21) {
4143 CookieManager.getInstance().removeAllCookies(null);
4145 CookieManager.getInstance().removeAllCookie();
4148 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4150 // Two commands must be used because `Runtime.exec()` does not like `*`.
4151 Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
4152 Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
4154 // Wait until the processes have finished.
4155 deleteCookiesProcess.waitFor();
4156 deleteCookiesJournalProcess.waitFor();
4157 } catch (Exception exception) {
4158 // Do nothing if an error is thrown.
4162 // Clear DOM storage.
4163 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
4164 // Ask `WebStorage` to clear the DOM storage.
4165 WebStorage webStorage = WebStorage.getInstance();
4166 webStorage.deleteAllData();
4168 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4170 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4171 Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
4173 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
4174 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
4175 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
4176 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
4177 Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
4179 // Wait until the processes have finished.
4180 deleteLocalStorageProcess.waitFor();
4181 deleteIndexProcess.waitFor();
4182 deleteQuotaManagerProcess.waitFor();
4183 deleteQuotaManagerJournalProcess.waitFor();
4184 deleteDatabaseProcess.waitFor();
4185 } catch (Exception exception) {
4186 // Do nothing if an error is thrown.
4190 // Clear form data if the API < 26.
4191 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
4192 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
4193 webViewDatabase.clearFormData();
4195 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4197 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
4198 Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
4199 Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
4201 // Wait until the processes have finished.
4202 deleteWebDataProcess.waitFor();
4203 deleteWebDataJournalProcess.waitFor();
4204 } catch (Exception exception) {
4205 // Do nothing if an error is thrown.
4210 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
4211 // Clear the cache from each WebView.
4212 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4213 // Get the WebView tab fragment.
4214 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4216 // Get the fragment view.
4217 View fragmentView = webViewTabFragment.getView();
4219 // Only clear the cache if the WebView exists.
4220 if (fragmentView != null) {
4221 // Get the nested scroll WebView from the tab fragment.
4222 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4224 // Clear the cache for this WebView.
4225 nestedScrollWebView.clearCache(true);
4229 // Manually delete the cache directories.
4231 // Delete the main cache directory.
4232 Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
4234 // Delete the secondary `Service Worker` cache directory.
4235 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4236 Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
4238 // Wait until the processes have finished.
4239 deleteCacheProcess.waitFor();
4240 deleteServiceWorkerProcess.waitFor();
4241 } catch (Exception exception) {
4242 // Do nothing if an error is thrown.
4246 // Wipe out each WebView.
4247 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4248 // Get the WebView tab fragment.
4249 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4251 // Get the fragment view.
4252 View fragmentView = webViewTabFragment.getView();
4254 // Only wipe out the WebView if it exists.
4255 if (fragmentView != null) {
4256 // Get the nested scroll WebView from the tab fragment.
4257 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4259 // Clear SSL certificate preferences for this WebView.
4260 nestedScrollWebView.clearSslPreferences();
4262 // Clear the back/forward history for this WebView.
4263 nestedScrollWebView.clearHistory();
4265 // Destroy the internal state of `mainWebView`.
4266 nestedScrollWebView.destroy();
4270 // Clear the custom headers.
4271 customHeaders.clear();
4273 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
4274 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
4275 if (clearEverything) {
4277 // Delete the folder.
4278 Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
4280 // Wait until the process has finished.
4281 deleteAppWebviewProcess.waitFor();
4282 } catch (Exception exception) {
4283 // Do nothing if an error is thrown.
4287 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
4288 if (Build.VERSION.SDK_INT >= 21) {
4289 finishAndRemoveTask();
4294 // Remove the terminated program from RAM. The status code is `0`.
4298 private void setCurrentWebView(int pageNumber) {
4299 // Get a handle for the shared preferences.
4300 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4302 // Get the theme preference.
4303 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
4305 // Get handles for the URL views.
4306 RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
4307 EditText urlEditText = findViewById(R.id.url_edittext);
4308 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
4310 // Stop the swipe to refresh indicator if it is running
4311 swipeRefreshLayout.setRefreshing(false);
4313 // Get the WebView tab fragment.
4314 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber);
4316 // Get the fragment view.
4317 View fragmentView = webViewTabFragment.getView();
4319 // Set the current WebView if the fragment view is not null.
4320 if (fragmentView != null) { // The fragment has been populated.
4321 // Store the current WebView.
4322 currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4324 // Update the status of swipe to refresh.
4325 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
4326 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top. It is updated every time the scroll changes.
4327 swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
4328 } else { // Swipe to refresh is disabled.
4329 // Disable the swipe refresh layout.
4330 swipeRefreshLayout.setEnabled(false);
4333 // Get a handle for the cookie manager.
4334 CookieManager cookieManager = CookieManager.getInstance();
4336 // Set the first-party cookie status.
4337 cookieManager.setAcceptCookie(currentWebView.getAcceptFirstPartyCookies());
4339 // Update the privacy icons. `true` redraws the icons in the app bar.
4340 updatePrivacyIcons(true);
4342 // Get a handle for the input method manager.
4343 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
4345 // Remove the lint warning below that the input method manager might be null.
4346 assert inputMethodManager != null;
4348 // Get the current URL.
4349 String url = currentWebView.getUrl();
4351 // Update the URL edit text if not loading a new intent. Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
4352 if (!loadingNewIntent) { // A new intent is not being loaded.
4353 if ((url == null) || url.equals("about:blank")) { // The WebView is blank.
4354 // Display the hint in the URL edit text.
4355 urlEditText.setText("");
4357 // Request focus for the URL text box.
4358 urlEditText.requestFocus();
4360 // Display the keyboard.
4361 inputMethodManager.showSoftInput(urlEditText, 0);
4362 } else { // The WebView has a loaded URL.
4363 // Clear the focus from the URL text box.
4364 urlEditText.clearFocus();
4366 // Hide the soft keyboard.
4367 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
4369 // Display the current URL in the URL text box.
4370 urlEditText.setText(url);
4372 // Highlight the URL text.
4375 } else { // A new intent is being loaded.
4376 // Reset the loading new intent tracker.
4377 loadingNewIntent = false;
4380 // Set the background to indicate the domain settings status.
4381 if (currentWebView.getDomainSettingsApplied()) {
4382 // Set a green background on the URL relative layout to indicate that custom domain settings are being used. The deprecated `.getDrawable()` must be used until the minimum API >= 21.
4384 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4386 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4389 urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
4391 } else { // The fragment has not been populated. Try again in 100 milliseconds.
4392 // Create a handler to set the current WebView.
4393 Handler setCurrentWebViewHandler = new Handler();
4395 // Create a runnable to set the current WebView.
4396 Runnable setCurrentWebWebRunnable = () -> {
4397 // Set the current WebView.
4398 setCurrentWebView(pageNumber);
4401 // Try setting the current WebView again after 100 milliseconds.
4402 setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100);
4407 public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url) {
4408 // Get handles for the activity views.
4409 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
4410 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
4411 RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
4412 ActionBar actionBar = getSupportActionBar();
4413 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
4414 EditText urlEditText = findViewById(R.id.url_edittext);
4415 TabLayout tabLayout = findViewById(R.id.tablayout);
4416 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
4418 // Remove the incorrect lint warning below that the action bar might be null.
4419 assert actionBar != null;
4421 // Get a handle for the activity
4422 Activity activity = this;
4424 // Get a handle for the input method manager.
4425 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
4427 // Instantiate the blocklist helper.
4428 BlocklistHelper blocklistHelper = new BlocklistHelper();
4430 // Remove the lint warning below that the input method manager might be null.
4431 assert inputMethodManager != null;
4433 // Get a handle for the shared preferences.
4434 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4436 // Initialize the favorite icon.
4437 nestedScrollWebView.initializeFavoriteIcon();
4439 // Set the app bar scrolling.
4440 nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
4442 // Allow pinch to zoom.
4443 nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
4445 // Hide zoom controls.
4446 nestedScrollWebView.getSettings().setDisplayZoomControls(false);
4448 // Don't allow mixed content (HTTP and HTTPS) on the same website.
4449 if (Build.VERSION.SDK_INT >= 21) {
4450 nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
4453 // Set the WebView to load in overview mode (zoomed out to the maximum width).
4454 nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
4456 // Explicitly disable geolocation.
4457 nestedScrollWebView.getSettings().setGeolocationEnabled(false);
4459 // Create a double-tap gesture detector to toggle full-screen mode.
4460 GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
4461 // Override `onDoubleTap()`. All other events are handled using the default settings.
4463 public boolean onDoubleTap(MotionEvent event) {
4464 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
4465 // Toggle the full screen browsing mode tracker.
4466 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
4468 // Toggle the full screen browsing mode.
4469 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
4470 // Store the swipe refresh layout top padding.
4471 swipeRefreshLayoutPaddingTop = swipeRefreshLayout.getPaddingTop();
4473 // Hide the app bar if specified.
4475 // Close the find on page bar if it is visible.
4476 closeFindOnPage(null);
4478 // Hide the tab linear layout.
4479 tabsLinearLayout.setVisibility(View.GONE);
4481 // Hide the action bar.
4484 // Check to see if app bar scrolling is disabled.
4485 if (!scrollAppBar) {
4486 // Remove the padding from the top of the swipe refresh layout.
4487 swipeRefreshLayout.setPadding(0, 0, 0, 0);
4491 // Hide the banner ad in the free flavor.
4492 if (BuildConfig.FLAVOR.contentEquals("free")) {
4493 AdHelper.hideAd(findViewById(R.id.adview));
4496 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
4497 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4499 /* Hide the system bars.
4500 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4501 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4502 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4503 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4505 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
4506 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4507 } else { // Switch to normal viewing mode.
4508 // Show the tab linear layout.
4509 tabsLinearLayout.setVisibility(View.VISIBLE);
4511 // Show the action bar.
4514 // Check to see if app bar scrolling is disabled.
4515 if (!scrollAppBar) {
4516 // Add the padding from the top of the swipe refresh layout.
4517 swipeRefreshLayout.setPadding(0, swipeRefreshLayoutPaddingTop, 0, 0);
4520 // Show the banner ad in the free flavor.
4521 if (BuildConfig.FLAVOR.contentEquals("free")) {
4523 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
4526 // Remove the `SYSTEM_UI` flags from the root frame layout.
4527 rootFrameLayout.setSystemUiVisibility(0);
4529 // Add the translucent status flag.
4530 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4533 // Consume the double-tap.
4535 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
4541 // Pass all touch events on the WebView through the double-tap gesture detector.
4542 nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
4543 // Call `performClick()` on the view, which is required for accessibility.
4544 view.performClick();
4546 // Send the event to the gesture detector.
4547 return doubleTapGestureDetector.onTouchEvent(event);
4550 // Register the WebView for a context menu. This is used to see link targets and download images.
4551 registerForContextMenu(nestedScrollWebView);
4553 // Allow the downloading of files.
4554 nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
4555 // Check if the download should be processed by an external app.
4556 if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app.
4557 // Create a download intent. Not specifying the action type will display the maximum number of options.
4558 Intent downloadIntent = new Intent();
4560 // Set the URI and the MIME type. Specifying `text/html` displays a good number of options.
4561 downloadIntent.setDataAndType(Uri.parse(downloadUrl), "text/html");
4563 // Flag the intent to open in a new task.
4564 downloadIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4566 // Show the chooser.
4567 startActivity(Intent.createChooser(downloadIntent, getString(R.string.open_with)));
4568 } else { // Download with Android's download manager.
4569 // Check to see if the WRITE_EXTERNAL_STORAGE permission has already been granted.
4570 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission has not been granted.
4571 // The WRITE_EXTERNAL_STORAGE permission needs to be requested.
4573 // Store the variables for future use by `onRequestPermissionsResult()`.
4574 this.downloadUrl = downloadUrl;
4575 downloadContentDisposition = contentDisposition;
4576 downloadContentLength = contentLength;
4578 // Show a dialog if the user has previously denied the permission.
4579 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
4580 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
4581 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
4583 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
4584 downloadLocationPermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.download_location));
4585 } else { // Show the permission request directly.
4586 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
4587 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
4589 } else { // The storage permission has already been granted.
4590 // Get a handle for the download file alert dialog.
4591 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(downloadUrl, contentDisposition, contentLength);
4593 // Show the download file alert dialog.
4594 downloadFileDialogFragment.show(getSupportFragmentManager(), getString(R.string.download));
4599 // Update the find on page count.
4600 nestedScrollWebView.setFindListener(new WebView.FindListener() {
4601 // Get a handle for `findOnPageCountTextView`.
4602 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
4605 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
4606 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
4607 // Set `findOnPageCountTextView` to `0/0`.
4608 findOnPageCountTextView.setText(R.string.zero_of_zero);
4609 } else if (isDoneCounting) { // There are matches.
4610 // `activeMatchOrdinal` is zero-based.
4611 int activeMatch = activeMatchOrdinal + 1;
4613 // Build the match string.
4614 String matchString = activeMatch + "/" + numberOfMatches;
4616 // Set `findOnPageCountTextView`.
4617 findOnPageCountTextView.setText(matchString);
4622 // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView.
4623 // Once the minimum API >= 23 this can be replaced with `nestedScrollWebView.setOnScrollChangeListener()`.
4624 nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
4625 if (nestedScrollWebView.getSwipeToRefresh()) {
4626 // Only enable swipe to refresh if the WebView is scrolled to the top.
4627 swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
4631 // Set the web chrome client.
4632 nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
4633 // Update the progress bar when a page is loading.
4635 public void onProgressChanged(WebView view, int progress) {
4636 // Inject the night mode CSS if night mode is enabled.
4637 if (nestedScrollWebView.getNightMode()) { // Night mode is enabled.
4638 // `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
4639 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
4640 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
4641 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
4642 nestedScrollWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
4643 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
4644 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
4645 // Initialize a handler to display `mainWebView`.
4646 Handler displayWebViewHandler = new Handler();
4648 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
4649 Runnable displayWebViewRunnable = () -> {
4650 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
4651 if (progressBar.getVisibility() == View.GONE) {
4652 nestedScrollWebView.setVisibility(View.VISIBLE);
4656 // Display the WebView after 500 milliseconds.
4657 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
4659 } else { // Night mode is disabled.
4660 // Display the nested scroll WebView if night mode is disabled.
4661 // Because of a race condition between `applyDomainSettings` and `onPageStarted`,
4662 // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled.
4663 nestedScrollWebView.setVisibility(View.VISIBLE);
4666 // Update the progress bar.
4667 progressBar.setProgress(progress);
4669 // Set the visibility of the progress bar.
4670 if (progress < 100) {
4671 // Show the progress bar.
4672 progressBar.setVisibility(View.VISIBLE);
4674 // Hide the progress bar.
4675 progressBar.setVisibility(View.GONE);
4677 //Stop the swipe to refresh indicator if it is running
4678 swipeRefreshLayout.setRefreshing(false);
4682 // Set the favorite icon when it changes.
4684 public void onReceivedIcon(WebView view, Bitmap icon) {
4685 // Only update the favorite icon if the website has finished loading.
4686 if (progressBar.getVisibility() == View.GONE) {
4687 // Store the new favorite icon.
4688 nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
4690 // Get the current page position.
4691 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
4693 // Get the current tab.
4694 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
4696 // Check to see if the tab has been populated.
4698 // Get the custom view from the tab.
4699 View tabView = tab.getCustomView();
4701 // Check to see if the custom tab view has been populated.
4702 if (tabView != null) {
4703 // Get the favorite icon image view from the tab.
4704 ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
4706 // Display the favorite icon in the tab.
4707 tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
4713 // Save a copy of the title when it changes.
4715 public void onReceivedTitle(WebView view, String title) {
4716 // Get the current page position.
4717 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
4719 // Get the current tab.
4720 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
4722 // Only populate the title text view if the tab has been fully created.
4724 // Get the custom view from the tab.
4725 View tabView = tab.getCustomView();
4727 // Remove the incorrect warning below that the current tab view might be null.
4728 assert tabView != null;
4730 // Get the title text view from the tab.
4731 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
4733 // Set the title according to the URL.
4734 if (title.equals("about:blank")) {
4735 // Set the title to indicate a new tab.
4736 tabTitleTextView.setText(R.string.new_tab);
4738 // Set the title as the tab text.
4739 tabTitleTextView.setText(title);
4744 // Enter full screen video.
4746 public void onShowCustomView(View video, CustomViewCallback callback) {
4747 // Get a handle for the full screen video frame layout.
4748 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
4750 // Set the full screen video flag.
4751 displayingFullScreenVideo = true;
4753 // Pause the ad if this is the free flavor.
4754 if (BuildConfig.FLAVOR.contentEquals("free")) {
4755 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
4756 AdHelper.pauseAd(findViewById(R.id.adview));
4759 // Hide the keyboard.
4760 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
4762 // Hide the main content relative layout.
4763 mainContentRelativeLayout.setVisibility(View.GONE);
4765 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
4766 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
4768 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
4769 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4771 /* Hide the system bars.
4772 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4773 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4774 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4775 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4777 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
4778 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4780 // Disable the sliding drawers.
4781 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
4783 // Add the video view to the full screen video frame layout.
4784 fullScreenVideoFrameLayout.addView(video);
4786 // Show the full screen video frame layout.
4787 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
4790 // Exit full screen video.
4792 public void onHideCustomView() {
4793 // Get a handle for the full screen video frame layout.
4794 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
4796 // Unset the full screen video flag.
4797 displayingFullScreenVideo = false;
4799 // Remove all the views from the full screen video frame layout.
4800 fullScreenVideoFrameLayout.removeAllViews();
4802 // Hide the full screen video frame layout.
4803 fullScreenVideoFrameLayout.setVisibility(View.GONE);
4805 // Enable the sliding drawers.
4806 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
4808 // Show the main content relative layout.
4809 mainContentRelativeLayout.setVisibility(View.VISIBLE);
4811 // Apply the appropriate full screen mode flags.
4812 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
4813 // Hide the app bar if specified.
4815 // Hide the tab linear layout.
4816 tabsLinearLayout.setVisibility(View.GONE);
4818 // Hide the action bar.
4822 // Hide the banner ad in the free flavor.
4823 if (BuildConfig.FLAVOR.contentEquals("free")) {
4824 AdHelper.hideAd(findViewById(R.id.adview));
4827 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
4828 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4830 /* Hide the system bars.
4831 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
4832 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
4833 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
4834 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
4836 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
4837 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
4838 } else { // Switch to normal viewing mode.
4839 // Remove the `SYSTEM_UI` flags from the root frame layout.
4840 rootFrameLayout.setSystemUiVisibility(0);
4842 // Add the translucent status flag.
4843 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
4846 // Reload the ad for the free flavor if not in full screen mode.
4847 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
4849 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
4855 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
4856 // Show the file chooser if the device is running API >= 21.
4857 if (Build.VERSION.SDK_INT >= 21) {
4858 // Store the file path callback.
4859 fileChooserCallback = filePathCallback;
4861 // Create an intent to open a chooser based ont the file chooser parameters.
4862 Intent fileChooserIntent = fileChooserParams.createIntent();
4864 // Open the file chooser. Currently only one `startActivityForResult` exists in this activity, so the request code, used to differentiate them, is simply `0`.
4865 startActivityForResult(fileChooserIntent, 0);
4871 nestedScrollWebView.setWebViewClient(new WebViewClient() {
4872 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
4873 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
4875 public boolean shouldOverrideUrlLoading(WebView view, String url) {
4876 // Sanitize the url.
4877 url = sanitizeUrl(url);
4879 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
4880 // Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed.
4881 boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
4883 // Check if the user agent has changed.
4884 if (userAgentChanged) {
4885 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
4886 nestedScrollWebView.loadUrl(url, customHeaders);
4888 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
4891 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
4894 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
4895 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
4896 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
4898 // Parse the url and set it as the data for the intent.
4899 emailIntent.setData(Uri.parse(url));
4901 // Open the email program in a new task instead of as part of Privacy Browser.
4902 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4905 startActivity(emailIntent);
4907 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
4909 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
4910 // Open the dialer and load the phone number, but wait for the user to place the call.
4911 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
4913 // Add the phone number to the intent.
4914 dialIntent.setData(Uri.parse(url));
4916 // Open the dialer in a new task instead of as part of Privacy Browser.
4917 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4920 startActivity(dialIntent);
4922 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
4924 } else { // Load a system chooser to select an app that can handle the URL.
4925 // Open an app that can handle the URL.
4926 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
4928 // Add the URL to the intent.
4929 genericIntent.setData(Uri.parse(url));
4931 // List all apps that can handle the URL instead of just opening the first one.
4932 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
4934 // Open the app in a new task instead of as part of Privacy Browser.
4935 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4937 // Start the app or display a snackbar if no app is available to handle the URL.
4939 startActivity(genericIntent);
4940 } catch (ActivityNotFoundException exception) {
4941 Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
4944 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
4949 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
4951 public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
4952 // Wait until the blocklists have been populated. When Privacy Browser is being resumed after having the process killed in the background it will try to load the URLs immediately.
4953 while (ultraPrivacy == null) {
4954 // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown.
4955 synchronized (this) {
4957 // Check to see if the blocklists have been populated after 100 ms.
4959 } catch (InterruptedException exception) {
4965 // Sanitize the URL.
4966 url = sanitizeUrl(url);
4968 // Get a handle for the navigation view.
4969 NavigationView navigationView = findViewById(R.id.navigationview);
4971 // Get a handle for the navigation menu.
4972 Menu navigationMenu = navigationView.getMenu();
4974 // Get a handle for the navigation requests menu item. The menu is 0 based.
4975 MenuItem navigationRequestsMenuItem = navigationMenu.getItem(5);
4977 // Create an empty web resource response to be used if the resource request is blocked.
4978 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
4980 // Reset the whitelist results tracker.
4981 String[] whitelistResultStringArray = null;
4983 // Initialize the third party request tracker.
4984 boolean isThirdPartyRequest = false;
4986 // Get the current URL. `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
4987 String currentBaseDomain = nestedScrollWebView.getCurrentDomainName();
4989 // Store a copy of the current domain for use in later requests.
4990 String currentDomain = currentBaseDomain;
4992 // Nobody is happy when comparing null strings.
4993 if ((currentBaseDomain != null) && (url != null)) {
4994 // Convert the request URL to a URI.
4995 Uri requestUri = Uri.parse(url);
4997 // Get the request host name.
4998 String requestBaseDomain = requestUri.getHost();
5000 // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
5001 if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
5002 // Determine the current base domain.
5003 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
5004 // Remove the first subdomain.
5005 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
5008 // Determine the request base domain.
5009 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
5010 // Remove the first subdomain.
5011 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
5014 // Update the third party request tracker.
5015 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
5019 // Get the current WebView page position.
5020 int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5022 // Determine if the WebView is currently displayed.
5023 boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
5025 // Block third-party requests if enabled.
5026 if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
5027 // Add the result to the resource requests.
5028 nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url});
5030 // Increment the blocked requests counters.
5031 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5032 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS);
5034 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5035 if (webViewDisplayed) {
5036 // Updating the UI must be run from the UI thread.
5037 activity.runOnUiThread(() -> {
5038 // Update the menu item titles.
5039 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5041 // Update the options menu if it has been populated.
5042 if (optionsMenu != null) {
5043 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5044 optionsMenu.findItem(R.id.block_all_third_party_requests).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
5045 getString(R.string.block_all_third_party_requests));
5050 // Return an empty web resource response.
5051 return emptyWebResourceResponse;
5054 // Check UltraPrivacy if it is enabled.
5055 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY)) {
5056 // Check the URL against UltraPrivacy.
5057 String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
5059 // Process the UltraPrivacy results.
5060 if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraPrivacy's blacklist.
5061 // Add the result to the resource requests.
5062 nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5063 ultraPrivacyResults[5]});
5065 // Increment the blocked requests counters.
5066 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5067 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRA_PRIVACY);
5069 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5070 if (webViewDisplayed) {
5071 // Updating the UI must be run from the UI thread.
5072 activity.runOnUiThread(() -> {
5073 // Update the menu item titles.
5074 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5076 // Update the options menu if it has been populated.
5077 if (optionsMenu != null) {
5078 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5079 optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
5084 // The resource request was blocked. Return an empty web resource response.
5085 return emptyWebResourceResponse;
5086 } else if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraPrivacy's whitelist.
5087 // Add a whitelist entry to the resource requests array.
5088 nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5089 ultraPrivacyResults[5]});
5091 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
5096 // Check EasyList if it is enabled.
5097 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST)) {
5098 // Check the URL against EasyList.
5099 String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
5101 // Process the EasyList results.
5102 if (easyListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyList's blacklist.
5103 // Add the result to the resource requests.
5104 nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]});
5106 // Increment the blocked requests counters.
5107 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5108 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_LIST);
5110 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5111 if (webViewDisplayed) {
5112 // Updating the UI must be run from the UI thread.
5113 activity.runOnUiThread(() -> {
5114 // Update the menu item titles.
5115 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5117 // Update the options menu if it has been populated.
5118 if (optionsMenu != null) {
5119 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5120 optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
5125 // The resource request was blocked. Return an empty web resource response.
5126 return emptyWebResourceResponse;
5127 } else if (easyListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyList's whitelist.
5128 // Update the whitelist result string array tracker.
5129 whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]};
5133 // Check EasyPrivacy if it is enabled.
5134 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY)) {
5135 // Check the URL against EasyPrivacy.
5136 String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
5138 // Process the EasyPrivacy results.
5139 if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyPrivacy's blacklist.
5140 // Add the result to the resource requests.
5141 nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
5142 easyPrivacyResults[5]});
5144 // Increment the blocked requests counters.
5145 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5146 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASY_PRIVACY);
5148 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5149 if (webViewDisplayed) {
5150 // Updating the UI must be run from the UI thread.
5151 activity.runOnUiThread(() -> {
5152 // Update the menu item titles.
5153 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5155 // Update the options menu if it has been populated.
5156 if (optionsMenu != null) {
5157 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5158 optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
5163 // The resource request was blocked. Return an empty web resource response.
5164 return emptyWebResourceResponse;
5165 } else if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyPrivacy's whitelist.
5166 // Update the whitelist result string array tracker.
5167 whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]};
5171 // Check Fanboy’s Annoyance List if it is enabled.
5172 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
5173 // Check the URL against Fanboy's Annoyance List.
5174 String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
5176 // Process the Fanboy's Annoyance List results.
5177 if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Annoyance List's blacklist.
5178 // Add the result to the resource requests.
5179 nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5180 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]});
5182 // Increment the blocked requests counters.
5183 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5184 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST);
5186 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5187 if (webViewDisplayed) {
5188 // Updating the UI must be run from the UI thread.
5189 activity.runOnUiThread(() -> {
5190 // Update the menu item titles.
5191 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5193 // Update the options menu if it has been populated.
5194 if (optionsMenu != null) {
5195 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5196 optionsMenu.findItem(R.id.fanboys_annoyance_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
5197 getString(R.string.fanboys_annoyance_list));
5202 // The resource request was blocked. Return an empty web resource response.
5203 return emptyWebResourceResponse;
5204 } else if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)){ // The resource request matched Fanboy's Annoyance List's whitelist.
5205 // Update the whitelist result string array tracker.
5206 whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5207 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
5209 } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
5210 // Check the URL against Fanboy's Annoyance List.
5211 String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
5213 // Process the Fanboy's Social Blocking List results.
5214 if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Social Blocking List's blacklist.
5215 // Add the result to the resource requests.
5216 nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5217 fanboysSocialListResults[4], fanboysSocialListResults[5]});
5219 // Increment the blocked requests counters.
5220 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5221 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST);
5223 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5224 if (webViewDisplayed) {
5225 // Updating the UI must be run from the UI thread.
5226 activity.runOnUiThread(() -> {
5227 // Update the menu item titles.
5228 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5230 // Update the options menu if it has been populated.
5231 if (optionsMenu != null) {
5232 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5233 optionsMenu.findItem(R.id.fanboys_social_blocking_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
5234 getString(R.string.fanboys_social_blocking_list));
5239 // The resource request was blocked. Return an empty web resource response.
5240 return emptyWebResourceResponse;
5241 } else if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched Fanboy's Social Blocking List's whitelist.
5242 // Update the whitelist result string array tracker.
5243 whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5244 fanboysSocialListResults[4], fanboysSocialListResults[5]};
5248 // Add the request to the log because it hasn't been processed by any of the previous checks.
5249 if (whitelistResultStringArray != null) { // The request was processed by a whitelist.
5250 nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
5251 } else { // The request didn't match any blocklist entry. Log it as a default request.
5252 nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_DEFAULT, url});
5255 // The resource request has not been blocked. `return null` loads the requested resource.
5259 // Handle HTTP authentication requests.
5261 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
5262 // Store the handler.
5263 nestedScrollWebView.setHttpAuthHandler(handler);
5265 // Instantiate an HTTP authentication dialog.
5266 DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId());
5268 // Show the HTTP authentication dialog.
5269 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
5273 public void onPageStarted(WebView view, String url, Bitmap favicon) {
5274 // Get the preferences.
5275 boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
5276 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
5278 // Get a handler for the app bar layout.
5279 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
5281 // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.
5283 // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
5284 swipeRefreshLayout.setPadding(0, 0, 0, 0);
5286 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5287 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
5289 // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated.
5290 int appBarHeight = appBarLayout.getHeight();
5292 // The swipe refresh layout must be manually moved below the app bar layout.
5293 swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
5295 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5296 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
5299 // Reset the list of resource requests.
5300 nestedScrollWebView.clearResourceRequests();
5302 // Reset the requests counters.
5303 nestedScrollWebView.resetRequestsCounters();
5305 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
5306 if (nestedScrollWebView.getNightMode()) {
5307 nestedScrollWebView.setVisibility(View.INVISIBLE);
5309 nestedScrollWebView.setVisibility(View.VISIBLE);
5312 // Hide the keyboard.
5313 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
5315 // Check to see if Privacy Browser is waiting on Orbot.
5316 if (!waitingForOrbot) { // Process the URL.
5317 // Get the current page position.
5318 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5320 // Update the URL text bar if the page is currently selected.
5321 if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
5322 // Clear the focus from the URL edit text.
5323 urlEditText.clearFocus();
5325 // Display the formatted URL text.
5326 urlEditText.setText(url);
5328 // Apply text highlighting to `urlTextBox`.
5332 // Reset the list of host IP addresses.
5333 nestedScrollWebView.clearCurrentIpAddresses();
5335 // Get a URI for the current URL.
5336 Uri currentUri = Uri.parse(url);
5338 // Get the IP addresses for the host.
5339 new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
5341 // Apply any custom domain settings if the URL was loaded by navigating history.
5342 if (nestedScrollWebView.getNavigatingHistory()) {
5343 // Reset navigating history.
5344 nestedScrollWebView.setNavigatingHistory(false);
5346 // Apply the domain settings.
5347 boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
5349 // Manually load the URL if the user agent has changed, which will have caused the previous URL to be reloaded.
5350 if (userAgentChanged) {
5355 // Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.)
5356 if (optionsMenu != null) {
5357 // Get a handle for the refresh menu item.
5358 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
5361 refreshMenuItem.setTitle(R.string.stop);
5363 // Get the app bar and theme preferences.
5364 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
5366 // If the icon is displayed in the AppBar, set it according to the theme.
5367 if (displayAdditionalAppBarIcons) {
5369 refreshMenuItem.setIcon(R.drawable.close_dark);
5371 refreshMenuItem.setIcon(R.drawable.close_light);
5379 public void onPageFinished(WebView view, String url) {
5380 // Flush any cookies to persistent storage. The cookie manager has become very lazy about flushing cookies in recent versions.
5381 if (nestedScrollWebView.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) {
5382 CookieManager.getInstance().flush();
5385 // Update the Refresh menu item if the options menu has been created.
5386 if (optionsMenu != null) {
5387 // Get a handle for the refresh menu item.
5388 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
5390 // Reset the Refresh title.
5391 refreshMenuItem.setTitle(R.string.refresh);
5393 // Get the app bar and theme preferences.
5394 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
5395 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
5397 // If the icon is displayed in the AppBar, reset it according to the theme.
5398 if (displayAdditionalAppBarIcons) {
5400 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
5402 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
5407 // Clear the cache and history if Incognito Mode is enabled.
5408 if (incognitoModeEnabled) {
5409 // Clear the cache. `true` includes disk files.
5410 nestedScrollWebView.clearCache(true);
5412 // Clear the back/forward history.
5413 nestedScrollWebView.clearHistory();
5415 // Manually delete cache folders.
5417 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
5418 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
5419 String privateDataDirectoryString = getApplicationInfo().dataDir;
5421 // Delete the main cache directory.
5422 Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
5424 // Delete the secondary `Service Worker` cache directory.
5425 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
5426 Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
5427 } catch (IOException e) {
5428 // Do nothing if an error is thrown.
5432 // Update the URL text box and apply domain settings if not waiting on Orbot.
5433 if (!waitingForOrbot) {
5434 // Get the current page position.
5435 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5437 // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
5438 if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
5439 !nestedScrollWebView.ignorePinnedDomainInformation()) {
5440 CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
5443 // Get the current URL from the nested scroll WebView. This is more accurate than using the URL passed into the method, which is sometimes not the final one.
5444 String currentUrl = nestedScrollWebView.getUrl();
5446 // Get the current tab.
5447 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
5449 // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
5450 // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
5451 // Probably some sort of race condition when Privacy Browser is being resumed.
5452 if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
5453 // Check to see if the URL is `about:blank`.
5454 if (currentUrl.equals("about:blank")) { // The WebView is blank.
5455 // Display the hint in the URL edit text.
5456 urlEditText.setText("");
5458 // Request focus for the URL text box.
5459 urlEditText.requestFocus();
5461 // Display the keyboard.
5462 inputMethodManager.showSoftInput(urlEditText, 0);
5464 // Apply the domain settings. This clears any settings from the previous domain.
5465 applyDomainSettings(nestedScrollWebView, "", true, false);
5467 // Only populate the title text view if the tab has been fully created.
5469 // Get the custom view from the tab.
5470 View tabView = tab.getCustomView();
5472 // Remove the incorrect warning below that the current tab view might be null.
5473 assert tabView != null;
5475 // Get the title text view from the tab.
5476 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
5478 // Set the title as the tab text.
5479 tabTitleTextView.setText(R.string.new_tab);
5481 } else { // The WebView has loaded a webpage.
5482 // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
5483 urlEditText.setText(currentUrl);
5485 // Apply text highlighting to the URL.
5488 // Only populate the title text view if the tab has been fully created.
5490 // Get the custom view from the tab.
5491 View tabView = tab.getCustomView();
5493 // Remove the incorrect warning below that the current tab view might be null.
5494 assert tabView != null;
5496 // Get the title text view from the tab.
5497 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
5499 // Set the title as the tab text. Sometimes `onReceivedTitle()` is not called, especially when navigating history.
5500 tabTitleTextView.setText(nestedScrollWebView.getTitle());
5507 // Handle SSL Certificate errors.
5509 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
5510 // Get the current website SSL certificate.
5511 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
5513 // Extract the individual pieces of information from the current website SSL certificate.
5514 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
5515 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
5516 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
5517 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
5518 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
5519 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
5520 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
5521 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
5523 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
5524 if (nestedScrollWebView.hasPinnedSslCertificate()) {
5525 // Get the pinned SSL certificate.
5526 ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
5528 // Extract the arrays from the array list.
5529 String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
5530 Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
5532 // Check if the current SSL certificate matches the pinned certificate.
5533 if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
5534 currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) &&
5535 currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) &&
5536 currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
5538 // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
5541 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
5542 // Store the SSL error handler.
5543 nestedScrollWebView.setSslErrorHandler(handler);
5545 // Instantiate an SSL certificate error alert dialog.
5546 DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
5548 // Show the SSL certificate error dialog.
5549 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
5554 // Check to see if this is the first page.
5555 if (pageNumber == 0) {
5556 // Set this nested scroll WebView as the current WebView.
5557 currentWebView = nestedScrollWebView;
5559 // Apply the app settings from the shared preferences.
5562 // Load the website if not waiting for Orbot to connect.
5563 if (!waitingForOrbot) {
5564 // Get the intent that started the app.
5565 Intent launchingIntent = getIntent();
5567 // Get the information from the intent.
5568 String launchingIntentAction = launchingIntent.getAction();
5569 Uri launchingIntentUriData = launchingIntent.getData();
5571 // If the intent action is a web search, perform the search.
5572 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
5573 // Create an encoded URL string.
5574 String encodedUrlString;
5576 // Sanitize the search input and convert it to a search.
5578 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
5579 } catch (UnsupportedEncodingException exception) {
5580 encodedUrlString = "";
5583 // Load the completed search URL.
5584 loadUrl(searchURL + encodedUrlString);
5585 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
5586 // Load the URL from the intent.
5587 loadUrl(launchingIntentUriData.toString());
5588 } else { // The is no URL in the intent.
5589 // Select the homepage based on the proxy through Orbot status.
5590 if (proxyThroughOrbot) {
5591 // Load the Tor homepage.
5592 loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
5594 // Load the normal homepage.
5595 loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
5599 } else { // This is not the first tab.
5600 // Apply the domain settings.
5601 applyDomainSettings(nestedScrollWebView, url, false, false);
5604 nestedScrollWebView.loadUrl(url, customHeaders);
5606 // Set the focus and display the keyboard if the URL is blank.
5607 if (url.equals("")) {
5608 // Request focus for the URL text box.
5609 urlEditText.requestFocus();
5611 // Create a display keyboard handler.
5612 Handler displayKeyboardHandler = new Handler();
5614 // Create a display keyboard runnable.
5615 Runnable displayKeyboardRunnable = () -> {
5616 // Display the keyboard.
5617 inputMethodManager.showSoftInput(urlEditText, 0);
5620 // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
5621 displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100);