2 * Copyright © 2015-2020 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.Dialog;
28 import android.app.DownloadManager;
29 import android.app.SearchManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageManager;
39 import android.content.res.Configuration;
40 import android.database.Cursor;
41 import android.graphics.Bitmap;
42 import android.graphics.BitmapFactory;
43 import android.graphics.Typeface;
44 import android.graphics.drawable.BitmapDrawable;
45 import android.graphics.drawable.Drawable;
46 import android.net.Uri;
47 import android.net.http.SslCertificate;
48 import android.net.http.SslError;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.os.Message;
53 import android.preference.PreferenceManager;
54 import android.print.PrintDocumentAdapter;
55 import android.print.PrintManager;
56 import android.text.Editable;
57 import android.text.Spanned;
58 import android.text.TextWatcher;
59 import android.text.style.ForegroundColorSpan;
60 import android.util.Patterns;
61 import android.view.ContextMenu;
62 import android.view.GestureDetector;
63 import android.view.KeyEvent;
64 import android.view.Menu;
65 import android.view.MenuItem;
66 import android.view.MotionEvent;
67 import android.view.View;
68 import android.view.ViewGroup;
69 import android.view.WindowManager;
70 import android.view.inputmethod.InputMethodManager;
71 import android.webkit.CookieManager;
72 import android.webkit.HttpAuthHandler;
73 import android.webkit.SslErrorHandler;
74 import android.webkit.ValueCallback;
75 import android.webkit.WebBackForwardList;
76 import android.webkit.WebChromeClient;
77 import android.webkit.WebResourceResponse;
78 import android.webkit.WebSettings;
79 import android.webkit.WebStorage;
80 import android.webkit.WebView;
81 import android.webkit.WebViewClient;
82 import android.webkit.WebViewDatabase;
83 import android.widget.ArrayAdapter;
84 import android.widget.CursorAdapter;
85 import android.widget.EditText;
86 import android.widget.FrameLayout;
87 import android.widget.ImageView;
88 import android.widget.LinearLayout;
89 import android.widget.ListView;
90 import android.widget.ProgressBar;
91 import android.widget.RadioButton;
92 import android.widget.RelativeLayout;
93 import android.widget.TextView;
95 import androidx.annotation.NonNull;
96 import androidx.appcompat.app.ActionBar;
97 import androidx.appcompat.app.ActionBarDrawerToggle;
98 import androidx.appcompat.app.AppCompatActivity;
99 import androidx.appcompat.widget.Toolbar;
100 import androidx.coordinatorlayout.widget.CoordinatorLayout;
101 import androidx.core.app.ActivityCompat;
102 import androidx.core.content.ContextCompat;
103 import androidx.core.view.GravityCompat;
104 import androidx.drawerlayout.widget.DrawerLayout;
105 import androidx.fragment.app.DialogFragment;
106 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
107 import androidx.viewpager.widget.ViewPager;
109 import com.google.android.material.appbar.AppBarLayout;
110 import com.google.android.material.floatingactionbutton.FloatingActionButton;
111 import com.google.android.material.navigation.NavigationView;
112 import com.google.android.material.snackbar.Snackbar;
113 import com.google.android.material.tabs.TabLayout;
115 import com.stoutner.privacybrowser.BuildConfig;
116 import com.stoutner.privacybrowser.R;
117 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
118 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
119 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
120 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
121 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
122 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
123 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
124 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
125 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
126 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
127 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
128 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
129 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
130 import com.stoutner.privacybrowser.dialogs.OpenDialog;
131 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
132 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
133 import com.stoutner.privacybrowser.dialogs.SaveDialog;
134 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
135 import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
136 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
137 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
138 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
139 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
140 import com.stoutner.privacybrowser.helpers.AdHelper;
141 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
142 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
143 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
144 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
145 import com.stoutner.privacybrowser.helpers.FileNameHelper;
146 import com.stoutner.privacybrowser.helpers.ProxyHelper;
147 import com.stoutner.privacybrowser.views.NestedScrollWebView;
149 import java.io.ByteArrayInputStream;
150 import java.io.ByteArrayOutputStream;
152 import java.io.IOException;
153 import java.io.UnsupportedEncodingException;
154 import java.net.MalformedURLException;
156 import java.net.URLDecoder;
157 import java.net.URLEncoder;
158 import java.util.ArrayList;
159 import java.util.Date;
160 import java.util.HashMap;
161 import java.util.HashSet;
162 import java.util.List;
163 import java.util.Map;
164 import java.util.Objects;
165 import java.util.Set;
167 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
168 EditBookmarkDialog.EditBookmarkListener, EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener,
169 OpenDialog.OpenListener, PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveWebpageListener,
170 StoragePermissionDialog.StoragePermissionDialogListener, UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
172 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
173 public static String orbotStatus = "unknown";
175 // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`.
176 public static WebViewPagerAdapter webViewPagerAdapter;
178 // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`. They are used in `onRestart()`.
179 public static boolean loadUrlOnRestart;
180 public static String urlToLoadOnRestart;
182 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
183 public static boolean restartFromBookmarksActivity;
185 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
186 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
187 public static String currentBookmarksFolder;
189 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
190 public final static int UNRECOGNIZED_USER_AGENT = -1;
191 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
192 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
193 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
194 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
195 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
197 // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
198 public final static int BROWSE_OPEN_REQUEST_CODE = 0;
199 public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
200 private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2;
202 // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
203 // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
204 // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
205 public static String proxyMode = ProxyHelper.NONE;
208 // The permission result request codes are used in `onCreateContextMenu()`, `onRequestPermissionResult()`, `onSaveWebpage()`, `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
209 private final int PERMISSION_OPEN_REQUEST_CODE = 0;
210 private final int PERMISSION_SAVE_URL_REQUEST_CODE = 1;
211 private final int PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE = 2;
212 private final int PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE = 3;
214 // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
215 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
216 private NestedScrollWebView currentWebView;
218 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
219 private final Map<String, String> customHeaders = new HashMap<>();
221 // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
222 private String searchURL;
224 // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
225 private Menu optionsMenu;
227 // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
228 private ArrayList<List<String[]>> easyList;
229 private ArrayList<List<String[]>> easyPrivacy;
230 private ArrayList<List<String[]>> fanboysAnnoyanceList;
231 private ArrayList<List<String[]>> fanboysSocialList;
232 private ArrayList<List<String[]>> ultraList;
233 private ArrayList<List<String[]>> ultraPrivacy;
235 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
236 private String webViewDefaultUserAgent;
238 // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
239 private boolean incognitoModeEnabled;
241 // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
242 private boolean fullScreenBrowsingModeEnabled;
244 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
245 private boolean inFullScreenBrowsingMode;
247 // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
248 private boolean hideAppBar;
249 private boolean scrollAppBar;
251 // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
252 private boolean loadingNewIntent;
254 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
255 private boolean reapplyDomainSettingsOnRestart;
257 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
258 private boolean reapplyAppSettingsOnRestart;
260 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
261 private boolean displayingFullScreenVideo;
263 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
264 private BroadcastReceiver orbotStatusBroadcastReceiver;
266 // The waiting for proxy boolean is used in `onResume()`, `initializeApp()` and `applyProxy()`.
267 private boolean waitingForProxy = false;
269 // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
270 private ActionBarDrawerToggle actionBarDrawerToggle;
272 // The color spans are used in `onCreate()` and `highlightUrlText()`.
273 private ForegroundColorSpan redColorSpan;
274 private ForegroundColorSpan initialGrayColorSpan;
275 private ForegroundColorSpan finalGrayColorSpan;
277 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
278 private int drawerHeaderPaddingLeftAndRight;
279 private int drawerHeaderPaddingTop;
280 private int drawerHeaderPaddingBottom;
282 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
283 // and `loadBookmarksFolder()`.
284 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
286 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
287 private Cursor bookmarksCursor;
289 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
290 private CursorAdapter bookmarksCursorAdapter;
292 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
293 private String oldFolderNameString;
295 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
296 private ValueCallback<Uri[]> fileChooserCallback;
298 // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
299 private int defaultProgressViewStartOffset;
300 private int defaultProgressViewEndOffset;
302 // The swipe refresh layout top padding is used when exiting full screen browsing mode. It is used in an inner class in `initializeWebView()`.
303 private int swipeRefreshLayoutPaddingTop;
305 // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
306 private boolean sanitizeGoogleAnalytics;
307 private boolean sanitizeFacebookClickIds;
308 private boolean sanitizeTwitterAmpRedirects;
310 // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()`
311 private String openFilePath;
312 private String saveWebpageUrl;
313 private String saveWebpageFilePath;
316 // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
317 @SuppressLint("ClickableViewAccessibility")
318 protected void onCreate(Bundle savedInstanceState) {
319 // Enable the drawing of the entire webpage. This makes it possible to save a website image. This must be done before anything else happens with the WebView.
320 if (Build.VERSION.SDK_INT >= 21) {
321 WebView.enableSlowWholeDocumentDraw();
324 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
325 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
327 // Get a handle for the shared preferences.
328 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
330 // Get the theme and screenshot preferences.
331 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
332 boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
334 // Disable screenshots if not allowed.
335 if (!allowScreenshots) {
336 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
339 // Set the activity theme.
341 setTheme(R.style.PrivacyBrowserDark);
343 setTheme(R.style.PrivacyBrowserLight);
346 // Run the default commands.
347 super.onCreate(savedInstanceState);
349 // Set the content view.
350 setContentView(R.layout.main_framelayout);
352 // Get handles for the views that need to be modified.
353 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
354 Toolbar toolbar = findViewById(R.id.toolbar);
355 ViewPager webViewPager = findViewById(R.id.webviewpager);
357 // Set the action bar. `SupportActionBar` must be used until the minimum API is >= 21.
358 setSupportActionBar(toolbar);
360 // Get a handle for the action bar.
361 ActionBar actionBar = getSupportActionBar();
363 // This is needed to get rid of the Android Studio warning that the action bar might be null.
364 assert actionBar != null;
366 // Add the custom layout, which shows the URL text bar.
367 actionBar.setCustomView(R.layout.url_app_bar);
368 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
370 // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded.
371 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
373 // Create the hamburger icon at the start of the AppBar.
374 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
376 // Initialize the web view pager adapter.
377 webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
379 // Set the pager adapter on the web view pager.
380 webViewPager.setAdapter(webViewPagerAdapter);
382 // Store up to 100 tabs in memory.
383 webViewPager.setOffscreenPageLimit(100);
385 // Populate the blocklists.
386 new PopulateBlocklists(this, this).execute();
390 protected void onNewIntent(Intent intent) {
391 // Run the default commands.
392 super.onNewIntent(intent);
394 // Replace the intent that started the app with this one.
397 // 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()`.
398 if (ultraPrivacy != null) {
399 // Get the information from the intent.
400 String intentAction = intent.getAction();
401 Uri intentUriData = intent.getData();
403 // Determine if this is a web search.
404 boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
406 // 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.
407 if (intentUriData != null || isWebSearch) {
408 // Get the shared preferences.
409 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
411 // Create a URL string.
414 // If the intent action is a web search, perform the search.
416 // Create an encoded URL string.
417 String encodedUrlString;
419 // Sanitize the search input and convert it to a search.
421 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
422 } catch (UnsupportedEncodingException exception) {
423 encodedUrlString = "";
426 // Add the base search URL.
427 url = searchURL + encodedUrlString;
428 } else { // The intent should contain a URL.
429 // Set the intent data as the URL.
430 url = intentUriData.toString();
433 // Add a new tab if specified in the preferences.
434 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
435 // Set the loading new intent flag.
436 loadingNewIntent = true;
439 addNewTab(url, true);
440 } else { // Load the URL in the current tab.
442 loadUrl(currentWebView, url);
445 // Get a handle for the drawer layout.
446 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
448 // Close the navigation drawer if it is open.
449 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
450 drawerLayout.closeDrawer(GravityCompat.START);
453 // Close the bookmarks drawer if it is open.
454 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
455 drawerLayout.closeDrawer(GravityCompat.END);
462 public void onRestart() {
463 // Run the default commands.
466 // Apply the app settings if returning from the Settings activity.
467 if (reapplyAppSettingsOnRestart) {
468 // Reset the reapply app settings on restart tracker.
469 reapplyAppSettingsOnRestart = false;
471 // Apply the app settings.
475 // Apply the domain settings if returning from the settings or domains activity.
476 if (reapplyDomainSettingsOnRestart) {
477 // Reset the reapply domain settings on restart tracker.
478 reapplyDomainSettingsOnRestart = false;
480 // Reapply the domain settings for each tab.
481 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
482 // Get the WebView tab fragment.
483 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
485 // Get the fragment view.
486 View fragmentView = webViewTabFragment.getView();
488 // Only reload the WebViews if they exist.
489 if (fragmentView != null) {
490 // Get the nested scroll WebView from the tab fragment.
491 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
493 // Reset the current domain name so the domain settings will be reapplied.
494 nestedScrollWebView.resetCurrentDomainName();
496 // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
497 if (nestedScrollWebView.getUrl() != null) {
498 applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
504 // Load the URL on restart (used when loading a bookmark).
505 if (loadUrlOnRestart) {
506 // Load the specified URL.
507 loadUrl(currentWebView, urlToLoadOnRestart);
509 // Reset the load on restart tracker.
510 loadUrlOnRestart = false;
513 // Update the bookmarks drawer if returning from the Bookmarks activity.
514 if (restartFromBookmarksActivity) {
515 // Get a handle for the drawer layout.
516 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
518 // Close the bookmarks drawer.
519 drawerLayout.closeDrawer(GravityCompat.END);
521 // Reload the bookmarks drawer.
522 loadBookmarksFolder();
524 // Reset `restartFromBookmarksActivity`.
525 restartFromBookmarksActivity = false;
528 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
529 updatePrivacyIcons(true);
532 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
534 public void onResume() {
535 // Run the default commands.
538 // Resume any WebViews.
539 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
540 // Get the WebView tab fragment.
541 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
543 // Get the fragment view.
544 View fragmentView = webViewTabFragment.getView();
546 // Only resume the WebViews if they exist (they won't when the app is first created).
547 if (fragmentView != null) {
548 // Get the nested scroll WebView from the tab fragment.
549 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
551 // Resume the nested scroll WebView JavaScript timers.
552 nestedScrollWebView.resumeTimers();
554 // Resume the nested scroll WebView.
555 nestedScrollWebView.onResume();
559 // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog.
560 if (!proxyMode.equals(ProxyHelper.NONE)) {
564 // Reapply any system UI flags and the ad in the free flavor.
565 if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode.
566 // Get a handle for the root frame layouts.
567 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
569 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
570 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
572 /* Hide the system bars.
573 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
574 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
575 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
576 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
578 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
579 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
580 } else if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode.
582 AdHelper.resumeAd(findViewById(R.id.adview));
587 public void onPause() {
588 // Run the default commands.
591 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
592 // Get the WebView tab fragment.
593 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
595 // Get the fragment view.
596 View fragmentView = webViewTabFragment.getView();
598 // Only pause the WebViews if they exist (they won't when the app is first created).
599 if (fragmentView != null) {
600 // Get the nested scroll WebView from the tab fragment.
601 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
603 // Pause the nested scroll WebView.
604 nestedScrollWebView.onPause();
606 // Pause the nested scroll WebView JavaScript timers.
607 nestedScrollWebView.pauseTimers();
611 // Pause the ad or it will continue to consume resources in the background on the free flavor.
612 if (BuildConfig.FLAVOR.contentEquals("free")) {
614 AdHelper.pauseAd(findViewById(R.id.adview));
619 public void onDestroy() {
620 // Unregister the orbot status broadcast receiver.
621 this.unregisterReceiver(orbotStatusBroadcastReceiver);
623 // Close the bookmarks cursor and database.
624 bookmarksCursor.close();
625 bookmarksDatabaseHelper.close();
627 // Run the default commands.
632 public boolean onCreateOptionsMenu(Menu menu) {
633 // Inflate the menu. This adds items to the action bar if it is present.
634 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
636 // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
639 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
640 updatePrivacyIcons(false);
642 // Get handles for the menu items.
643 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
644 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
645 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
646 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
647 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
648 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
649 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
651 // Only display third-party cookies if API >= 21
652 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
654 // Only display the form data menu items if the API < 26.
655 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
656 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
658 // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
659 clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
661 // Only show Ad Consent if this is the free flavor.
662 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
664 // Get the shared preferences.
665 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
667 // Get the dark theme and app bar preferences..
668 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
669 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
671 // 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.
672 if (displayAdditionalAppBarIcons) {
673 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
674 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
675 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
676 } else { //Do not display the additional icons.
677 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
678 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
679 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
682 // Replace Refresh with Stop if a URL is already loading.
683 if (currentWebView != null && currentWebView.getProgress() != 100) {
685 refreshMenuItem.setTitle(R.string.stop);
687 // If the icon is displayed in the AppBar, set it according to the theme.
688 if (displayAdditionalAppBarIcons) {
690 refreshMenuItem.setIcon(R.drawable.close_dark);
692 refreshMenuItem.setIcon(R.drawable.close_light);
702 public boolean onPrepareOptionsMenu(Menu menu) {
703 // Get handles for the menu items.
704 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
705 MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
706 MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
707 MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
708 MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
709 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
710 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
711 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
712 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
713 MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
714 MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
715 MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
716 MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
717 MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
718 MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist);
719 MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
720 MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
721 MenuItem proxyMenuItem = menu.findItem(R.id.proxy);
722 MenuItem userAgentMenuItem = menu.findItem(R.id.user_agent);
723 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
724 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
725 MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
726 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
727 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
729 // Get a handle for the cookie manager.
730 CookieManager cookieManager = CookieManager.getInstance();
732 // Initialize the current user agent string and the font size.
733 String currentUserAgent = getString(R.string.user_agent_privacy_browser);
736 // 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.
737 if (currentWebView != null) {
738 // Set the add or edit domain text.
739 if (currentWebView.getDomainSettingsApplied()) {
740 addOrEditDomain.setTitle(R.string.edit_domain_settings);
742 addOrEditDomain.setTitle(R.string.add_domain_settings);
745 // Get the current user agent from the WebView.
746 currentUserAgent = currentWebView.getSettings().getUserAgentString();
748 // Get the current font size from the
749 fontSize = currentWebView.getSettings().getTextZoom();
751 // Set the status of the menu item checkboxes.
752 domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
753 saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
754 easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
755 easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
756 fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
757 fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
758 ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
759 ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
760 blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
761 swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
762 wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
763 displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
764 nightModeMenuItem.setChecked(currentWebView.getNightMode());
766 // Initialize the display names for the blocklists with the number of blocked requests.
767 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
768 easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
769 easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
770 fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
771 fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
772 ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
773 ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
774 blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
776 // Only modify third-party cookies if the API >= 21.
777 if (Build.VERSION.SDK_INT >= 21) {
778 // Set the status of the third-party cookies checkbox.
779 thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
781 // Enable third-party cookies if first-party cookies are enabled.
782 thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
785 // Enable DOM Storage if JavaScript is enabled.
786 domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
789 // Set the checked status of the first party cookies menu item.
790 firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
792 // Enable Clear Cookies if there are any.
793 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
795 // 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`.
796 String privateDataDirectoryString = getApplicationInfo().dataDir;
798 // Get a count of the number of files in the Local Storage directory.
799 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
800 int localStorageDirectoryNumberOfFiles = 0;
801 if (localStorageDirectory.exists()) {
802 // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
803 localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
806 // Get a count of the number of files in the IndexedDB directory.
807 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
808 int indexedDBDirectoryNumberOfFiles = 0;
809 if (indexedDBDirectory.exists()) {
810 // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
811 indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
814 // Enable Clear DOM Storage if there is any.
815 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
817 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
818 if (Build.VERSION.SDK_INT < 26) {
819 // Get the WebView database.
820 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
822 // Enable the clear form data menu item if there is anything to clear.
823 clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
826 // Enable Clear Data if any of the submenu items are enabled.
827 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
829 // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
830 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
832 // Set the proxy title and check the applied proxy.
834 case ProxyHelper.NONE:
835 // Set the proxy title.
836 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
838 // Check the proxy None radio button.
839 menu.findItem(R.id.proxy_none).setChecked(true);
842 case ProxyHelper.TOR:
843 // Set the proxy title.
844 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
846 // Check the proxy Tor radio button.
847 menu.findItem(R.id.proxy_tor).setChecked(true);
850 case ProxyHelper.I2P:
851 // Set the proxy title.
852 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
854 // Check the proxy I2P radio button.
855 menu.findItem(R.id.proxy_i2p).setChecked(true);
858 case ProxyHelper.CUSTOM:
859 // Set the proxy title.
860 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
862 // Check the proxy Custom radio button.
863 menu.findItem(R.id.proxy_custom).setChecked(true);
867 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
868 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
869 // Update the user agent menu item title.
870 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
872 // Select the Privacy Browser radio box.
873 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
874 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
875 // Update the user agent menu item title.
876 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
878 // Select the WebView Default radio box.
879 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
880 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
881 // Update the user agent menu item title.
882 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
884 // Select the Firefox on Android radio box.
885 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
886 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
887 // Update the user agent menu item title.
888 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
890 // Select the Chrome on Android radio box.
891 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
892 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
893 // Update the user agent menu item title.
894 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
896 // Select the Safari on iOS radio box.
897 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
898 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
899 // Update the user agent menu item title.
900 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
902 // Select the Firefox on Linux radio box.
903 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
904 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
905 // Update the user agent menu item title.
906 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
908 // Select the Chromium on Linux radio box.
909 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
910 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
911 // Update the user agent menu item title.
912 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
914 // Select the Firefox on Windows radio box.
915 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
916 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
917 // Update the user agent menu item title.
918 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
920 // Select the Chrome on Windows radio box.
921 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
922 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
923 // Update the user agent menu item title.
924 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
926 // Select the Edge on Windows radio box.
927 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
928 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
929 // Update the user agent menu item title.
930 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
932 // Select the Internet on Windows radio box.
933 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
934 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
935 // Update the user agent menu item title.
936 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
938 // Select the Safari on macOS radio box.
939 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
940 } else { // Custom user agent.
941 // Update the user agent menu item title.
942 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
944 // Select the Custom radio box.
945 menu.findItem(R.id.user_agent_custom).setChecked(true);
948 // Set the font size title.
949 fontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
951 // Run all the other default commands.
952 super.onPrepareOptionsMenu(menu);
959 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
960 @SuppressLint("SetJavaScriptEnabled")
961 public boolean onOptionsItemSelected(MenuItem menuItem) {
962 // Get the selected menu item ID.
963 int menuItemId = menuItem.getItemId();
965 // Get a handle for the shared preferences.
966 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
968 // Get a handle for the cookie manager.
969 CookieManager cookieManager = CookieManager.getInstance();
971 // Run the commands that correlate to the selected menu item.
972 switch (menuItemId) {
973 case R.id.toggle_javascript:
974 // Toggle the JavaScript status.
975 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
977 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
978 updatePrivacyIcons(true);
980 // Display a `Snackbar`.
981 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled.
982 Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
983 } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled.
984 Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
985 } else { // Privacy mode.
986 Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
989 // Reload the current WebView.
990 currentWebView.reload();
992 // Consume the event.
996 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
997 // Reload the current WebView.
998 currentWebView.reload();
999 } else { // The stop button was pushed.
1000 // Stop the loading of the WebView.
1001 currentWebView.stopLoading();
1004 // Consume the event.
1007 case R.id.bookmarks:
1008 // Get a handle for the drawer layout.
1009 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
1011 // Open the bookmarks drawer.
1012 drawerLayout.openDrawer(GravityCompat.END);
1014 // Consume the event.
1017 case R.id.toggle_first_party_cookies:
1018 // Switch the first-party cookie status.
1019 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1021 // Store the first-party cookie status.
1022 currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
1024 // Update the menu checkbox.
1025 menuItem.setChecked(cookieManager.acceptCookie());
1027 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1028 updatePrivacyIcons(true);
1030 // Display a snackbar.
1031 if (cookieManager.acceptCookie()) { // First-party cookies are enabled.
1032 Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1033 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
1034 Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1035 } else { // Privacy mode.
1036 Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1039 // Reload the current WebView.
1040 currentWebView.reload();
1042 // Consume the event.
1045 case R.id.toggle_third_party_cookies:
1046 if (Build.VERSION.SDK_INT >= 21) {
1047 // Switch the status of thirdPartyCookiesEnabled.
1048 cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
1050 // Update the menu checkbox.
1051 menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1053 // Display a snackbar.
1054 if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
1055 Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1057 Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1060 // Reload the current WebView.
1061 currentWebView.reload();
1062 } // Else do nothing because SDK < 21.
1064 // Consume the event.
1067 case R.id.toggle_dom_storage:
1068 // Toggle the status of domStorageEnabled.
1069 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1071 // Update the menu checkbox.
1072 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1074 // Update the privacy icon. `true` refreshes the app bar icons.
1075 updatePrivacyIcons(true);
1077 // Display a snackbar.
1078 if (currentWebView.getSettings().getDomStorageEnabled()) {
1079 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1081 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1084 // Reload the current WebView.
1085 currentWebView.reload();
1087 // Consume the event.
1090 // Form data can be removed once the minimum API >= 26.
1091 case R.id.toggle_save_form_data:
1092 // Switch the status of saveFormDataEnabled.
1093 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1095 // Update the menu checkbox.
1096 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1098 // Display a snackbar.
1099 if (currentWebView.getSettings().getSaveFormData()) {
1100 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1102 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1105 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1106 updatePrivacyIcons(true);
1108 // Reload the current WebView.
1109 currentWebView.reload();
1111 // Consume the event.
1114 case R.id.clear_cookies:
1115 Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1116 .setAction(R.string.undo, v -> {
1117 // Do nothing because everything will be handled by `onDismissed()` below.
1119 .addCallback(new Snackbar.Callback() {
1120 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1122 public void onDismissed(Snackbar snackbar, int event) {
1123 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1124 // Delete the cookies, which command varies by SDK.
1125 if (Build.VERSION.SDK_INT < 21) {
1126 cookieManager.removeAllCookie();
1128 cookieManager.removeAllCookies(null);
1135 // Consume the event.
1138 case R.id.clear_dom_storage:
1139 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1140 .setAction(R.string.undo, v -> {
1141 // Do nothing because everything will be handled by `onDismissed()` below.
1143 .addCallback(new Snackbar.Callback() {
1144 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1146 public void onDismissed(Snackbar snackbar, int event) {
1147 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1148 // Delete the DOM Storage.
1149 WebStorage webStorage = WebStorage.getInstance();
1150 webStorage.deleteAllData();
1152 // Initialize a handler to manually delete the DOM storage files and directories.
1153 Handler deleteDomStorageHandler = new Handler();
1155 // Setup a runnable to manually delete the DOM storage files and directories.
1156 Runnable deleteDomStorageRunnable = () -> {
1158 // Get a handle for the runtime.
1159 Runtime runtime = Runtime.getRuntime();
1161 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1162 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1163 String privateDataDirectoryString = getApplicationInfo().dataDir;
1165 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1166 Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1168 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1169 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1170 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1171 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1172 Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1174 // Wait for the processes to finish.
1175 deleteLocalStorageProcess.waitFor();
1176 deleteIndexProcess.waitFor();
1177 deleteQuotaManagerProcess.waitFor();
1178 deleteQuotaManagerJournalProcess.waitFor();
1179 deleteDatabasesProcess.waitFor();
1180 } catch (Exception exception) {
1181 // Do nothing if an error is thrown.
1185 // Manually delete the DOM storage files after 200 milliseconds.
1186 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1192 // Consume the event.
1195 // Form data can be remove once the minimum API >= 26.
1196 case R.id.clear_form_data:
1197 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1198 .setAction(R.string.undo, v -> {
1199 // Do nothing because everything will be handled by `onDismissed()` below.
1201 .addCallback(new Snackbar.Callback() {
1202 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1204 public void onDismissed(Snackbar snackbar, int event) {
1205 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1206 // Delete the form data.
1207 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1208 mainWebViewDatabase.clearFormData();
1214 // Consume the event.
1218 // Toggle the EasyList status.
1219 currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1221 // Update the menu checkbox.
1222 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1224 // Reload the current WebView.
1225 currentWebView.reload();
1227 // Consume the event.
1230 case R.id.easyprivacy:
1231 // Toggle the EasyPrivacy status.
1232 currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1234 // Update the menu checkbox.
1235 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1237 // Reload the current WebView.
1238 currentWebView.reload();
1240 // Consume the event.
1243 case R.id.fanboys_annoyance_list:
1244 // Toggle Fanboy's Annoyance List status.
1245 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1247 // Update the menu checkbox.
1248 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1250 // Update the staus of Fanboy's Social Blocking List.
1251 MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
1252 fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1254 // Reload the current WebView.
1255 currentWebView.reload();
1257 // Consume the event.
1260 case R.id.fanboys_social_blocking_list:
1261 // Toggle Fanboy's Social Blocking List status.
1262 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1264 // Update the menu checkbox.
1265 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1267 // Reload the current WebView.
1268 currentWebView.reload();
1270 // Consume the event.
1273 case R.id.ultralist:
1274 // Toggle the UltraList status.
1275 currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1277 // Update the menu checkbox.
1278 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1280 // Reload the current WebView.
1281 currentWebView.reload();
1283 // Consume the event.
1286 case R.id.ultraprivacy:
1287 // Toggle the UltraPrivacy status.
1288 currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1290 // Update the menu checkbox.
1291 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1293 // Reload the current WebView.
1294 currentWebView.reload();
1296 // Consume the event.
1299 case R.id.block_all_third_party_requests:
1300 //Toggle the third-party requests blocker status.
1301 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1303 // Update the menu checkbox.
1304 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1306 // Reload the current WebView.
1307 currentWebView.reload();
1309 // Consume the event.
1312 case R.id.proxy_none:
1313 // Update the proxy mode.
1314 proxyMode = ProxyHelper.NONE;
1316 // Apply the proxy mode.
1319 // Consume the event.
1322 case R.id.proxy_tor:
1323 // Update the proxy mode.
1324 proxyMode = ProxyHelper.TOR;
1326 // Apply the proxy mode.
1329 // Consume the event.
1332 case R.id.proxy_i2p:
1333 // Update the proxy mode.
1334 proxyMode = ProxyHelper.I2P;
1336 // Apply the proxy mode.
1339 // Consume the event.
1342 case R.id.proxy_custom:
1343 // Update the proxy mode.
1344 proxyMode = ProxyHelper.CUSTOM;
1346 // Apply the proxy mode.
1349 // Consume the event.
1352 case R.id.user_agent_privacy_browser:
1353 // Update the user agent.
1354 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1356 // Reload the current WebView.
1357 currentWebView.reload();
1359 // Consume the event.
1362 case R.id.user_agent_webview_default:
1363 // Update the user agent.
1364 currentWebView.getSettings().setUserAgentString("");
1366 // Reload the current WebView.
1367 currentWebView.reload();
1369 // Consume the event.
1372 case R.id.user_agent_firefox_on_android:
1373 // Update the user agent.
1374 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1376 // Reload the current WebView.
1377 currentWebView.reload();
1379 // Consume the event.
1382 case R.id.user_agent_chrome_on_android:
1383 // Update the user agent.
1384 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1386 // Reload the current WebView.
1387 currentWebView.reload();
1389 // Consume the event.
1392 case R.id.user_agent_safari_on_ios:
1393 // Update the user agent.
1394 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1396 // Reload the current WebView.
1397 currentWebView.reload();
1399 // Consume the event.
1402 case R.id.user_agent_firefox_on_linux:
1403 // Update the user agent.
1404 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1406 // Reload the current WebView.
1407 currentWebView.reload();
1409 // Consume the event.
1412 case R.id.user_agent_chromium_on_linux:
1413 // Update the user agent.
1414 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1416 // Reload the current WebView.
1417 currentWebView.reload();
1419 // Consume the event.
1422 case R.id.user_agent_firefox_on_windows:
1423 // Update the user agent.
1424 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1426 // Reload the current WebView.
1427 currentWebView.reload();
1429 // Consume the event.
1432 case R.id.user_agent_chrome_on_windows:
1433 // Update the user agent.
1434 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1436 // Reload the current WebView.
1437 currentWebView.reload();
1439 // Consume the event.
1442 case R.id.user_agent_edge_on_windows:
1443 // Update the user agent.
1444 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1446 // Reload the current WebView.
1447 currentWebView.reload();
1449 // Consume the event.
1452 case R.id.user_agent_internet_explorer_on_windows:
1453 // Update the user agent.
1454 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1456 // Reload the current WebView.
1457 currentWebView.reload();
1459 // Consume the event.
1462 case R.id.user_agent_safari_on_macos:
1463 // Update the user agent.
1464 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1466 // Reload the current WebView.
1467 currentWebView.reload();
1469 // Consume the event.
1472 case R.id.user_agent_custom:
1473 // Update the user agent.
1474 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1476 // Reload the current WebView.
1477 currentWebView.reload();
1479 // Consume the event.
1482 case R.id.font_size:
1483 // Instantiate the font size dialog.
1484 DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1486 // Show the font size dialog.
1487 fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1489 // Consume the event.
1492 case R.id.swipe_to_refresh:
1493 // Toggle the stored status of swipe to refresh.
1494 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1496 // Get a handle for the swipe refresh layout.
1497 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
1499 // Update the swipe refresh layout.
1500 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
1501 // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
1502 swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
1503 } else { // Swipe to refresh is disabled.
1504 // Disable the swipe refresh layout.
1505 swipeRefreshLayout.setEnabled(false);
1508 // Consume the event.
1511 case R.id.wide_viewport:
1512 // Toggle the viewport.
1513 currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1515 // Consume the event.
1518 case R.id.display_images:
1519 if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
1520 // Disable loading of images.
1521 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1523 // Reload the website to remove existing images.
1524 currentWebView.reload();
1525 } else { // Images are not currently loaded automatically.
1526 // Enable loading of images. Missing images will be loaded without the need for a reload.
1527 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1530 // Consume the event.
1533 case R.id.night_mode:
1534 // Toggle night mode.
1535 currentWebView.setNightMode(!currentWebView.getNightMode());
1537 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
1538 if (currentWebView.getNightMode()) { // Night mode is enabled, which requires JavaScript.
1539 // Enable JavaScript.
1540 currentWebView.getSettings().setJavaScriptEnabled(true);
1541 } else if (currentWebView.getDomainSettingsApplied()) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
1542 // Apply the JavaScript preference that was stored the last time domain settings were loaded.
1543 currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
1544 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
1545 // Apply the JavaScript preference.
1546 currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
1549 // Update the privacy icons.
1550 updatePrivacyIcons(false);
1552 // Reload the website.
1553 currentWebView.reload();
1555 // Consume the event.
1558 case R.id.find_on_page:
1559 // Get a handle for the views.
1560 Toolbar toolbar = findViewById(R.id.toolbar);
1561 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1562 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1564 // Set the minimum height of the find on page linear layout to match the toolbar.
1565 findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1567 // Hide the toolbar.
1568 toolbar.setVisibility(View.GONE);
1570 // Show the find on page linear layout.
1571 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1573 // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
1574 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1575 findOnPageEditText.postDelayed(() -> {
1576 // Set the focus on `findOnPageEditText`.
1577 findOnPageEditText.requestFocus();
1579 // Get a handle for the input method manager.
1580 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1582 // Remove the lint warning below that the input method manager might be null.
1583 assert inputMethodManager != null;
1585 // Display the keyboard. `0` sets no input flags.
1586 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1589 // Consume the event.
1593 // Get a print manager instance.
1594 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1596 // Remove the lint error below that print manager might be null.
1597 assert printManager != null;
1599 // Create a print document adapter from the current WebView.
1600 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1602 // Print the document.
1603 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1605 // Consume the event.
1609 // Instantiate the save dialog.
1610 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
1611 currentWebView.getAcceptFirstPartyCookies());
1613 // Show the save dialog. It must be named `save_dialog` so that the file picked can update the file name.
1614 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1616 // Consume the event.
1619 case R.id.save_as_archive:
1620 // Instantiate the save webpage archive dialog.
1621 DialogFragment saveWebpageArchiveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
1622 currentWebView.getAcceptFirstPartyCookies());
1624 // Show the save webpage archive dialog. It must be named `save_dialog` so that the file picked can update the file name.
1625 saveWebpageArchiveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1627 // Consume the event.
1630 case R.id.save_as_image:
1631 // Instantiate the save webpage image dialog. It must be named `save_webpage` so that the file picked can update the file name.
1632 DialogFragment saveWebpageImageDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getCurrentUrl(), currentWebView.getSettings().getUserAgentString(),
1633 currentWebView.getAcceptFirstPartyCookies());
1635 // Show the save webpage image dialog. It must be named `save_dialog` so that the file picked can update the file name.
1636 saveWebpageImageDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1638 // Consume the event.
1641 case R.id.add_to_homescreen:
1642 // Instantiate the create home screen shortcut dialog.
1643 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1644 currentWebView.getFavoriteOrDefaultIcon());
1646 // Show the create home screen shortcut dialog.
1647 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1649 // Consume the event.
1652 case R.id.view_source:
1653 // Create an intent to launch the view source activity.
1654 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1656 // Add the variables to the intent.
1657 viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
1658 viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
1661 startActivity(viewSourceIntent);
1663 // Consume the event.
1666 case R.id.share_url:
1667 // Setup the share string.
1668 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1670 // Create the share intent.
1671 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1672 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1673 shareIntent.setType("text/plain");
1676 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1678 // Consume the event.
1681 case R.id.open_with_app:
1682 // Open the URL with an outside app.
1683 openWithApp(currentWebView.getUrl());
1685 // Consume the event.
1688 case R.id.open_with_browser:
1689 // Open the URL with an outside browser.
1690 openWithBrowser(currentWebView.getUrl());
1692 // Consume the event.
1695 case R.id.add_or_edit_domain:
1696 if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings.
1697 // Reapply the domain settings on returning to `MainWebViewActivity`.
1698 reapplyDomainSettingsOnRestart = true;
1700 // Create an intent to launch the domains activity.
1701 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1703 // Add the extra information to the intent.
1704 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1705 domainsIntent.putExtra("close_on_back", true);
1706 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1708 // Get the current certificate.
1709 SslCertificate sslCertificate = currentWebView.getCertificate();
1711 // Check to see if the SSL certificate is populated.
1712 if (sslCertificate != null) {
1713 // Extract the certificate to strings.
1714 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1715 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1716 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1717 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1718 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1719 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1720 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1721 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1723 // Add the certificate to the intent.
1724 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1725 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1726 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1727 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1728 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1729 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1730 domainsIntent.putExtra("ssl_start_date", startDateLong);
1731 domainsIntent.putExtra("ssl_end_date", endDateLong);
1734 // Check to see if the current IP addresses have been received.
1735 if (currentWebView.hasCurrentIpAddresses()) {
1736 // Add the current IP addresses to the intent.
1737 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1741 startActivity(domainsIntent);
1742 } else { // Add a new domain.
1743 // Apply the new domain settings on returning to `MainWebViewActivity`.
1744 reapplyDomainSettingsOnRestart = true;
1746 // Get the current domain
1747 Uri currentUri = Uri.parse(currentWebView.getUrl());
1748 String currentDomain = currentUri.getHost();
1750 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1751 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1753 // Create the domain and store the database ID.
1754 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1756 // Create an intent to launch the domains activity.
1757 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1759 // Add the extra information to the intent.
1760 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1761 domainsIntent.putExtra("close_on_back", true);
1762 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1764 // Get the current certificate.
1765 SslCertificate sslCertificate = currentWebView.getCertificate();
1767 // Check to see if the SSL certificate is populated.
1768 if (sslCertificate != null) {
1769 // Extract the certificate to strings.
1770 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1771 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1772 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1773 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1774 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1775 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1776 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1777 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1779 // Add the certificate to the intent.
1780 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1781 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1782 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1783 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1784 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1785 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1786 domainsIntent.putExtra("ssl_start_date", startDateLong);
1787 domainsIntent.putExtra("ssl_end_date", endDateLong);
1790 // Check to see if the current IP addresses have been received.
1791 if (currentWebView.hasCurrentIpAddresses()) {
1792 // Add the current IP addresses to the intent.
1793 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1797 startActivity(domainsIntent);
1800 // Consume the event.
1803 case R.id.ad_consent:
1804 // Instantiate the ad consent dialog.
1805 DialogFragment adConsentDialogFragment = new AdConsentDialog();
1807 // Display the ad consent dialog.
1808 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1810 // Consume the event.
1814 // Don't consume the event.
1815 return super.onOptionsItemSelected(menuItem);
1819 // removeAllCookies is deprecated, but it is required for API < 21.
1821 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1822 // Get the menu item ID.
1823 int menuItemId = menuItem.getItemId();
1825 // Get a handle for the shared preferences.
1826 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1828 // Run the commands that correspond to the selected menu item.
1829 switch (menuItemId) {
1830 case R.id.clear_and_exit:
1831 // Clear and exit Privacy Browser.
1836 // Load the homepage.
1837 loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1841 if (currentWebView.canGoBack()) {
1842 // Get the current web back forward list.
1843 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1845 // Get the previous entry URL.
1846 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
1848 // Apply the domain settings.
1849 applyDomainSettings(currentWebView, previousUrl, false, false);
1851 // Load the previous website in the history.
1852 currentWebView.goBack();
1857 if (currentWebView.canGoForward()) {
1858 // Get the current web back forward list.
1859 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1861 // Get the next entry URL.
1862 String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
1864 // Apply the domain settings.
1865 applyDomainSettings(currentWebView, nextUrl, false, false);
1867 // Load the next website in the history.
1868 currentWebView.goForward();
1873 // Instantiate the URL history dialog.
1874 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
1876 // Show the URL history dialog.
1877 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
1881 // Instantiate the open file dialog.
1882 DialogFragment openDialogFragment = new OpenDialog();
1884 // Show the open file dialog.
1885 openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
1889 // Populate the resource requests.
1890 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
1892 // Create an intent to launch the Requests activity.
1893 Intent requestsIntent = new Intent(this, RequestsActivity.class);
1895 // Add the block third-party requests status to the intent.
1896 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1899 startActivity(requestsIntent);
1902 case R.id.downloads:
1903 // Launch the system Download Manager.
1904 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1906 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
1907 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1910 startActivity(downloadManagerIntent);
1914 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
1915 reapplyDomainSettingsOnRestart = true;
1917 // Launch the domains activity.
1918 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1920 // Add the extra information to the intent.
1921 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1923 // Get the current certificate.
1924 SslCertificate sslCertificate = currentWebView.getCertificate();
1926 // Check to see if the SSL certificate is populated.
1927 if (sslCertificate != null) {
1928 // Extract the certificate to strings.
1929 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1930 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1931 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1932 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1933 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1934 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1935 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1936 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1938 // Add the certificate to the intent.
1939 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1940 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1941 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1942 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1943 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1944 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1945 domainsIntent.putExtra("ssl_start_date", startDateLong);
1946 domainsIntent.putExtra("ssl_end_date", endDateLong);
1949 // Check to see if the current IP addresses have been received.
1950 if (currentWebView.hasCurrentIpAddresses()) {
1951 // Add the current IP addresses to the intent.
1952 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1956 startActivity(domainsIntent);
1960 // Set the flag to reapply app settings on restart when returning from Settings.
1961 reapplyAppSettingsOnRestart = true;
1963 // Set the flag to reapply the domain settings on restart when returning from Settings.
1964 reapplyDomainSettingsOnRestart = true;
1966 // Launch the settings activity.
1967 Intent settingsIntent = new Intent(this, SettingsActivity.class);
1968 startActivity(settingsIntent);
1971 case R.id.import_export:
1972 // Launch the import/export activity.
1973 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
1974 startActivity(importExportIntent);
1978 // Launch the logcat activity.
1979 Intent logcatIntent = new Intent(this, LogcatActivity.class);
1980 startActivity(logcatIntent);
1984 // Launch `GuideActivity`.
1985 Intent guideIntent = new Intent(this, GuideActivity.class);
1986 startActivity(guideIntent);
1990 // Create an intent to launch the about activity.
1991 Intent aboutIntent = new Intent(this, AboutActivity.class);
1993 // Create a string array for the blocklist versions.
1994 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],
1995 ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
1997 // Add the blocklist versions to the intent.
1998 aboutIntent.putExtra("blocklist_versions", blocklistVersions);
2001 startActivity(aboutIntent);
2005 // Get a handle for the drawer layout.
2006 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2008 // Close the navigation drawer.
2009 drawerLayout.closeDrawer(GravityCompat.START);
2014 public void onPostCreate(Bundle savedInstanceState) {
2015 // Run the default commands.
2016 super.onPostCreate(savedInstanceState);
2018 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
2019 actionBarDrawerToggle.syncState();
2023 public void onConfigurationChanged(@NonNull Configuration newConfig) {
2024 // Run the default commands.
2025 super.onConfigurationChanged(newConfig);
2027 // Get the status bar pixel size.
2028 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
2029 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
2031 // Get the resource density.
2032 float screenDensity = getResources().getDisplayMetrics().density;
2034 // Recalculate the drawer header padding.
2035 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
2036 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
2037 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
2039 // Reload the ad for the free flavor if not in full screen mode.
2040 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2041 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2042 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2045 // `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:
2046 // https://code.google.com/p/android/issues/detail?id=20493#c8
2047 // ActivityCompat.invalidateOptionsMenu(this);
2051 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2052 // Store the hit test result.
2053 final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2055 // Define the URL strings.
2056 final String imageUrl;
2057 final String linkUrl;
2059 // Get handles for the system managers.
2060 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2062 // Remove the lint errors below that the clipboard manager might be null.
2063 assert clipboardManager != null;
2065 // Process the link according to the type.
2066 switch (hitTestResult.getType()) {
2067 // `SRC_ANCHOR_TYPE` is a link.
2068 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2069 // Get the target URL.
2070 linkUrl = hitTestResult.getExtra();
2072 // Set the target URL as the title of the `ContextMenu`.
2073 menu.setHeaderTitle(linkUrl);
2075 // Add an Open in New Tab entry.
2076 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2077 // Load the link URL in a new tab and move to it.
2078 addNewTab(linkUrl, true);
2080 // Consume the event.
2084 // Add an Open in Background entry.
2085 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2086 // Load the link URL in a new tab but do not move to it.
2087 addNewTab(linkUrl, false);
2089 // Consume the event.
2093 // Add an Open with App entry.
2094 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2095 openWithApp(linkUrl);
2097 // Consume the event.
2101 // Add an Open with Browser entry.
2102 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2103 openWithBrowser(linkUrl);
2105 // Consume the event.
2109 // Add a Copy URL entry.
2110 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2111 // Save the link URL in a `ClipData`.
2112 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2114 // Set the `ClipData` as the clipboard's primary clip.
2115 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2117 // Consume the event.
2121 // Add a Save URL entry.
2122 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2123 // Instantiate the save dialog.
2124 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, linkUrl, currentWebView.getSettings().getUserAgentString(),
2125 currentWebView.getAcceptFirstPartyCookies());
2127 // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
2128 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
2130 // Consume the event.
2134 // Add an empty Cancel entry, which by default closes the context menu.
2135 menu.add(R.string.cancel);
2138 // `IMAGE_TYPE` is an image.
2139 case WebView.HitTestResult.IMAGE_TYPE:
2140 // Get the image URL.
2141 imageUrl = hitTestResult.getExtra();
2143 // Set the image URL as the title of the context menu.
2144 menu.setHeaderTitle(imageUrl);
2146 // Add an Open in New Tab entry.
2147 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2148 // Load the image in a new tab.
2149 addNewTab(imageUrl, true);
2151 // Consume the event.
2155 // Add an Open with App entry.
2156 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2157 // Open the image URL with an external app.
2158 openWithApp(imageUrl);
2160 // Consume the event.
2164 // Add an Open with Browser entry.
2165 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2166 // Open the image URL with an external browser.
2167 openWithBrowser(imageUrl);
2169 // Consume the event.
2173 // Add a View Image entry.
2174 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2175 // Load the image in the current tab.
2176 loadUrl(currentWebView, imageUrl);
2178 // Consume the event.
2182 // Add a Save Image entry.
2183 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2184 // Instantiate the save dialog.
2185 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
2186 currentWebView.getAcceptFirstPartyCookies());
2188 // Show the save dialog. It must be named `save_dialog` so that the file picked can update the file name.
2189 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
2191 // Consume the event.
2195 // Add a Copy URL entry.
2196 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2197 // Save the image URL in a clip data.
2198 ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2200 // Set the clip data as the clipboard's primary clip.
2201 clipboardManager.setPrimaryClip(imageTypeClipData);
2203 // Consume the event.
2207 // Add an empty Cancel entry, which by default closes the context menu.
2208 menu.add(R.string.cancel);
2211 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2212 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2213 // Get the image URL.
2214 imageUrl = hitTestResult.getExtra();
2216 // Instantiate a handler.
2217 Handler handler = new Handler();
2219 // Get a message from the handler.
2220 Message message = handler.obtainMessage();
2222 // Request the image details from the last touched node be returned in the message.
2223 currentWebView.requestFocusNodeHref(message);
2225 // Get the link URL from the message data.
2226 linkUrl = message.getData().getString("url");
2228 // Set the link URL as the title of the context menu.
2229 menu.setHeaderTitle(linkUrl);
2231 // Add an Open in New Tab entry.
2232 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2233 // Load the link URL in a new tab and move to it.
2234 addNewTab(linkUrl, true);
2236 // Consume the event.
2240 // Add an Open in Background entry.
2241 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2242 // Lod the link URL in a new tab but do not move to it.
2243 addNewTab(linkUrl, false);
2245 // Consume the event.
2249 // Add an Open Image in New Tab entry.
2250 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2251 // Load the image in a new tab and move to it.
2252 addNewTab(imageUrl, true);
2254 // Consume the event.
2258 // Add an Open with App entry.
2259 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2260 // Open the link URL with an external app.
2261 openWithApp(linkUrl);
2263 // Consume the event.
2267 // Add an Open with Browser entry.
2268 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2269 // Open the link URL with an external browser.
2270 openWithBrowser(linkUrl);
2272 // Consume the event.
2276 // Add a View Image entry.
2277 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2278 // View the image in the current tab.
2279 loadUrl(currentWebView, imageUrl);
2281 // Consume the event.
2285 // Add a Save Image entry.
2286 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2287 // Instantiate the save dialog.
2288 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
2289 currentWebView.getAcceptFirstPartyCookies());
2291 // Show the save raw dialog. It must be named `save_dialog` so that the file picked can update the file name.
2292 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
2294 // Consume the event.
2298 // Add a Copy URL entry.
2299 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2300 // Save the link URL in a clip data.
2301 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2303 // Set the clip data as the clipboard's primary clip.
2304 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2306 // Consume the event.
2310 // Add a Save URL entry.
2311 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2312 // Instantiate the save dialog.
2313 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, linkUrl, currentWebView.getSettings().getUserAgentString(),
2314 currentWebView.getAcceptFirstPartyCookies());
2316 // Show the save raw dialog. It must be named `save_dialog` so that the file picked can update the file name.
2317 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
2319 // Consume the event.
2323 // Add an empty Cancel entry, which by default closes the context menu.
2324 menu.add(R.string.cancel);
2327 case WebView.HitTestResult.EMAIL_TYPE:
2328 // Get the target URL.
2329 linkUrl = hitTestResult.getExtra();
2331 // Set the target URL as the title of the `ContextMenu`.
2332 menu.setHeaderTitle(linkUrl);
2334 // Add a Write Email entry.
2335 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2336 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2337 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2339 // Parse the url and set it as the data for the `Intent`.
2340 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2342 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2343 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2346 startActivity(emailIntent);
2348 // Consume the event.
2352 // Add a Copy Email Address entry.
2353 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2354 // Save the email address in a `ClipData`.
2355 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2357 // Set the `ClipData` as the clipboard's primary clip.
2358 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2360 // Consume the event.
2364 // Add an empty Cancel entry, which by default closes the context menu.
2365 menu.add(R.string.cancel);
2371 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2372 // Get a handle for the bookmarks list view.
2373 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2376 Dialog dialog = dialogFragment.getDialog();
2378 // Remove the incorrect lint warning below that the dialog might be null.
2379 assert dialog != null;
2381 // Get the views from the dialog fragment.
2382 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2383 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2385 // Extract the strings from the edit texts.
2386 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2387 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2389 // Create a favorite icon byte array output stream.
2390 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2392 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2393 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2395 // Convert the favorite icon byte array stream to a byte array.
2396 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2398 // Display the new bookmark below the current items in the (0 indexed) list.
2399 int newBookmarkDisplayOrder = bookmarksListView.getCount();
2401 // Create the bookmark.
2402 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2404 // Update the bookmarks cursor with the current contents of this folder.
2405 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2407 // Update the list view.
2408 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2410 // Scroll to the new bookmark.
2411 bookmarksListView.setSelection(newBookmarkDisplayOrder);
2415 public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2416 // Get a handle for the bookmarks list view.
2417 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2420 Dialog dialog = dialogFragment.getDialog();
2422 // Remove the incorrect lint warning below that the dialog might be null.
2423 assert dialog != null;
2425 // Get handles for the views in the dialog fragment.
2426 EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
2427 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
2428 ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
2430 // Get new folder name string.
2431 String folderNameString = createFolderNameEditText.getText().toString();
2433 // Create a folder icon bitmap.
2434 Bitmap folderIconBitmap;
2436 // Set the folder icon bitmap according to the dialog.
2437 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
2438 // Get the default folder icon drawable.
2439 Drawable folderIconDrawable = folderIconImageView.getDrawable();
2441 // Convert the folder icon drawable to a bitmap drawable.
2442 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2444 // Convert the folder icon bitmap drawable to a bitmap.
2445 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2446 } else { // Use the WebView favorite icon.
2447 // Copy the favorite icon bitmap to the folder icon bitmap.
2448 folderIconBitmap = favoriteIconBitmap;
2451 // Create a folder icon byte array output stream.
2452 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2454 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2455 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2457 // Convert the folder icon byte array stream to a byte array.
2458 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2460 // Move all the bookmarks down one in the display order.
2461 for (int i = 0; i < bookmarksListView.getCount(); i++) {
2462 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2463 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2466 // Create the folder, which will be placed at the top of the `ListView`.
2467 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2469 // Update the bookmarks cursor with the current contents of this folder.
2470 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2472 // Update the `ListView`.
2473 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2475 // Scroll to the new folder.
2476 bookmarksListView.setSelection(0);
2480 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) {
2482 Dialog dialog = dialogFragment.getDialog();
2484 // Remove the incorrect lint warning below that the dialog might be null.
2485 assert dialog != null;
2487 // Get handles for the views from the dialog.
2488 EditText editBookmarkNameEditText = dialog.findViewById(R.id.edit_bookmark_name_edittext);
2489 EditText editBookmarkUrlEditText = dialog.findViewById(R.id.edit_bookmark_url_edittext);
2490 RadioButton currentBookmarkIconRadioButton = dialog.findViewById(R.id.edit_bookmark_current_icon_radiobutton);
2492 // Store the bookmark strings.
2493 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
2494 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
2496 // Update the bookmark.
2497 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
2498 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
2499 } else { // Update the bookmark using the `WebView` favorite icon.
2500 // Create a favorite icon byte array output stream.
2501 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2503 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2504 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
2506 // Convert the favorite icon byte array stream to a byte array.
2507 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
2509 // Update the bookmark and the favorite icon.
2510 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
2513 // Update the bookmarks cursor with the current contents of this folder.
2514 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2516 // Update the list view.
2517 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2521 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
2523 Dialog dialog = dialogFragment.getDialog();
2525 // Remove the incorrect lint warning below that the dialog might be null.
2526 assert dialog != null;
2528 // Get handles for the views from `dialogFragment`.
2529 EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
2530 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
2531 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
2532 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
2534 // Get the new folder name.
2535 String newFolderNameString = editFolderNameEditText.getText().toString();
2537 // Check if the favorite icon has changed.
2538 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
2539 // Update the name in the database.
2540 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2541 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
2542 // Create the new folder icon Bitmap.
2543 Bitmap folderIconBitmap;
2545 // Populate the new folder icon bitmap.
2546 if (defaultFolderIconRadioButton.isChecked()) {
2547 // Get the default folder icon drawable.
2548 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2550 // Convert the folder icon drawable to a bitmap drawable.
2551 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2553 // Convert the folder icon bitmap drawable to a bitmap.
2554 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2555 } else { // Use the `WebView` favorite icon.
2556 // Copy the favorite icon bitmap to the folder icon bitmap.
2557 folderIconBitmap = favoriteIconBitmap;
2560 // Create a folder icon byte array output stream.
2561 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2563 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2564 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2566 // Convert the folder icon byte array stream to a byte array.
2567 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2569 // Update the folder icon in the database.
2570 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2571 } else { // The folder icon and the name have changed.
2572 // Get the new folder icon `Bitmap`.
2573 Bitmap folderIconBitmap;
2574 if (defaultFolderIconRadioButton.isChecked()) {
2575 // Get the default folder icon drawable.
2576 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2578 // Convert the folder icon drawable to a bitmap drawable.
2579 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2581 // Convert the folder icon bitmap drawable to a bitmap.
2582 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2583 } else { // Use the `WebView` favorite icon.
2584 // Copy the favorite icon bitmap to the folder icon bitmap.
2585 folderIconBitmap = favoriteIconBitmap;
2588 // Create a folder icon byte array output stream.
2589 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2591 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2592 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2594 // Convert the folder icon byte array stream to a byte array.
2595 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2597 // Update the folder name and icon in the database.
2598 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2601 // Update the bookmarks cursor with the current contents of this folder.
2602 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2604 // Update the `ListView`.
2605 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2608 // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
2610 public void onBackPressed() {
2611 // Get a handle for the drawer layout and the tab layout.
2612 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2613 TabLayout tabLayout = findViewById(R.id.tablayout);
2615 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
2616 // Close the navigation drawer.
2617 drawerLayout.closeDrawer(GravityCompat.START);
2618 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
2619 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
2620 // close the bookmarks drawer.
2621 drawerLayout.closeDrawer(GravityCompat.END);
2622 } else { // A subfolder is displayed.
2623 // Place the former parent folder in `currentFolder`.
2624 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
2626 // Load the new folder.
2627 loadBookmarksFolder();
2629 } else if (displayingFullScreenVideo) { // A full screen video is shown.
2630 // Get a handle for the layouts.
2631 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
2632 RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
2633 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
2635 // Re-enable the screen timeout.
2636 fullScreenVideoFrameLayout.setKeepScreenOn(false);
2638 // Unset the full screen video flag.
2639 displayingFullScreenVideo = false;
2641 // Remove all the views from the full screen video frame layout.
2642 fullScreenVideoFrameLayout.removeAllViews();
2644 // Hide the full screen video frame layout.
2645 fullScreenVideoFrameLayout.setVisibility(View.GONE);
2647 // Enable the sliding drawers.
2648 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
2650 // Show the main content relative layout.
2651 mainContentRelativeLayout.setVisibility(View.VISIBLE);
2653 // Apply the appropriate full screen mode flags.
2654 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
2655 // Hide the app bar if specified.
2657 // Get handles for the views.
2658 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
2659 ActionBar actionBar = getSupportActionBar();
2661 // Remove the incorrect lint warning below that the action bar might be null.
2662 assert actionBar != null;
2664 // Hide the tab linear layout.
2665 tabsLinearLayout.setVisibility(View.GONE);
2667 // Hide the action bar.
2671 // Hide the banner ad in the free flavor.
2672 if (BuildConfig.FLAVOR.contentEquals("free")) {
2673 AdHelper.hideAd(findViewById(R.id.adview));
2676 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
2677 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2679 /* Hide the system bars.
2680 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2681 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
2682 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2683 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2685 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
2686 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2687 } else { // Switch to normal viewing mode.
2688 // Remove the `SYSTEM_UI` flags from the root frame layout.
2689 rootFrameLayout.setSystemUiVisibility(0);
2691 // Add the translucent status flag.
2692 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2695 // Reload the ad for the free flavor if not in full screen mode.
2696 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2698 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2700 } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history.
2701 // Get the current web back forward list.
2702 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2704 // Get the previous entry URL.
2705 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2707 // Apply the domain settings.
2708 applyDomainSettings(currentWebView, previousUrl, false, false);
2711 currentWebView.goBack();
2712 } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs.
2713 // Close the current tab.
2715 } else { // There isn't anything to do in Privacy Browser.
2716 // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
2717 if (Build.VERSION.SDK_INT >= 21) {
2718 finishAndRemoveTask();
2723 // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted.
2728 // Process the results of a file browse.
2730 public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
2731 // Run the default commands.
2732 super.onActivityResult(requestCode, resultCode, returnedIntent);
2734 // Run the commands that correlate to the specified request code.
2735 switch (requestCode) {
2736 case BROWSE_FILE_UPLOAD_REQUEST_CODE:
2737 // File uploads only work on API >= 21.
2738 if (Build.VERSION.SDK_INT >= 21) {
2739 // Pass the file to the WebView.
2740 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
2744 case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
2745 // Don't do anything if the user pressed back from the file picker.
2746 if (resultCode == Activity.RESULT_OK) {
2747 // Get a handle for the save dialog fragment.
2748 DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
2750 // Only update the file name if the dialog still exists.
2751 if (saveWebpageDialogFragment != null) {
2752 // Get a handle for the save webpage dialog.
2753 Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
2755 // Remove the incorrect lint warning below that the dialog might be null.
2756 assert saveWebpageDialog != null;
2758 // Get a handle for the file name edit text.
2759 EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
2760 TextView fileExistsWarningTextView = saveWebpageDialog.findViewById(R.id.file_exists_warning_textview);
2762 // Instantiate the file name helper.
2763 FileNameHelper fileNameHelper = new FileNameHelper();
2765 // Get the file path if it isn't null.
2766 if (returnedIntent.getData() != null) {
2767 // Convert the file name URI to a file name path.
2768 String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
2770 // Set the file name path as the text of the file name edit text.
2771 fileNameEditText.setText(fileNamePath);
2773 // Move the cursor to the end of the file name edit text.
2774 fileNameEditText.setSelection(fileNamePath.length());
2776 // Hide the file exists warning.
2777 fileExistsWarningTextView.setVisibility(View.GONE);
2783 case BROWSE_OPEN_REQUEST_CODE:
2784 // Don't do anything if the user pressed back from the file picker.
2785 if (resultCode == Activity.RESULT_OK) {
2786 // Get a handle for the open dialog fragment.
2787 DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
2789 // Only update the file name if the dialog still exists.
2790 if (openDialogFragment != null) {
2791 // Get a handle for the open dialog.
2792 Dialog openDialog = openDialogFragment.getDialog();
2794 // Remove the incorrect lint warning below that the dialog might be null.
2795 assert openDialog != null;
2797 // Get a handle for the file name edit text.
2798 EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
2800 // Instantiate the file name helper.
2801 FileNameHelper fileNameHelper = new FileNameHelper();
2803 // Get the file path if it isn't null.
2804 if (returnedIntent.getData() != null) {
2805 // Convert the file name URI to a file name path.
2806 String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
2808 // Set the file name path as the text of the file name edit text.
2809 fileNameEditText.setText(fileNamePath);
2811 // Move the cursor to the end of the file name edit text.
2812 fileNameEditText.setSelection(fileNamePath.length());
2820 private void loadUrlFromTextBox() {
2821 // Get a handle for the URL edit text.
2822 EditText urlEditText = findViewById(R.id.url_edittext);
2824 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
2825 String unformattedUrlString = urlEditText.getText().toString().trim();
2827 // Initialize the formatted URL string.
2830 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
2831 if (unformattedUrlString.startsWith("content://")) { // This is a Content URL.
2832 // Load the entire content URL.
2833 url = unformattedUrlString;
2834 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2835 unformattedUrlString.startsWith("file://")) { // This is a standard URL.
2836 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
2837 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2838 unformattedUrlString = "https://" + unformattedUrlString;
2841 // Initialize `unformattedUrl`.
2842 URL unformattedUrl = null;
2844 // 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.
2846 unformattedUrl = new URL(unformattedUrlString);
2847 } catch (MalformedURLException e) {
2848 e.printStackTrace();
2851 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2852 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2853 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2854 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2855 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2856 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2859 Uri.Builder uri = new Uri.Builder();
2860 uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2862 // Decode the URI as a UTF-8 string in.
2864 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2865 } catch (UnsupportedEncodingException exception) {
2866 // Do nothing. The formatted URL string will remain blank.
2868 } else if (!unformattedUrlString.isEmpty()){ // This is not a URL, but rather a search string.
2869 // Create an encoded URL String.
2870 String encodedUrlString;
2872 // Sanitize the search input.
2874 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2875 } catch (UnsupportedEncodingException exception) {
2876 encodedUrlString = "";
2879 // Add the base search URL.
2880 url = searchURL + encodedUrlString;
2883 // 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.
2884 urlEditText.clearFocus();
2887 loadUrl(currentWebView, url);
2890 private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) {
2891 // Sanitize the URL.
2892 url = sanitizeUrl(url);
2894 // Apply the domain settings.
2895 applyDomainSettings(nestedScrollWebView, url, true, false);
2898 nestedScrollWebView.loadUrl(url, customHeaders);
2901 public void findPreviousOnPage(View view) {
2902 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
2903 currentWebView.findNext(false);
2906 public void findNextOnPage(View view) {
2907 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2908 currentWebView.findNext(true);
2911 public void closeFindOnPage(View view) {
2912 // Get a handle for the views.
2913 Toolbar toolbar = findViewById(R.id.toolbar);
2914 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2915 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2917 // Delete the contents of `find_on_page_edittext`.
2918 findOnPageEditText.setText(null);
2920 // Clear the highlighted phrases if the WebView is not null.
2921 if (currentWebView != null) {
2922 currentWebView.clearMatches();
2925 // Hide the find on page linear layout.
2926 findOnPageLinearLayout.setVisibility(View.GONE);
2928 // Show the toolbar.
2929 toolbar.setVisibility(View.VISIBLE);
2931 // Get a handle for the input method manager.
2932 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2934 // Remove the lint warning below that the input method manager might be null.
2935 assert inputMethodManager != null;
2937 // Hide the keyboard.
2938 inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2942 public void onApplyNewFontSize(DialogFragment dialogFragment) {
2944 Dialog dialog = dialogFragment.getDialog();
2946 // Remove the incorrect lint warning below tha the dialog might be null.
2947 assert dialog != null;
2949 // Get a handle for the font size edit text.
2950 EditText fontSizeEditText = dialog.findViewById(R.id.font_size_edittext);
2952 // Initialize the new font size variable with the current font size.
2953 int newFontSize = currentWebView.getSettings().getTextZoom();
2955 // Get the font size from the edit text.
2957 newFontSize = Integer.parseInt(fontSizeEditText.getText().toString());
2958 } catch (Exception exception) {
2959 // If the edit text does not contain a valid font size do nothing.
2962 // Apply the new font size.
2963 currentWebView.getSettings().setTextZoom(newFontSize);
2967 public void onOpen(DialogFragment dialogFragment) {
2969 Dialog dialog = dialogFragment.getDialog();
2971 // Remove the incorrect lint warning below that the dialog might be null.
2972 assert dialog != null;
2974 // Get a handle for the file name edit text.
2975 EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
2977 // Get the file path string.
2978 openFilePath = fileNameEditText.getText().toString();
2980 // Check to see if the storage permission is needed.
2981 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
2983 currentWebView.loadUrl("file://" + openFilePath);
2984 } else { // The storage permission has not been granted.
2985 // Get the external private directory file.
2986 File externalPrivateDirectoryFile = getExternalFilesDir(null);
2988 // Remove the incorrect lint error below that the file might be null.
2989 assert externalPrivateDirectoryFile != null;
2991 // Get the external private directory string.
2992 String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
2994 // Check to see if the file path is in the external private directory.
2995 if (openFilePath.startsWith(externalPrivateDirectory)) { // the file path is in the external private directory.
2997 currentWebView.loadUrl("file://" + openFilePath);
2998 } else { // The file path is in a public directory.
2999 // Check if the user has previously denied the storage permission.
3000 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3001 // Instantiate the storage permission alert dialog.
3002 DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN);
3004 // Show the storage permission alert dialog. The permission will be requested the the dialog is closed.
3005 storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
3006 } else { // Show the permission request directly.
3007 // Request the write external storage permission. The file will be opened when it finishes.
3008 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE);
3015 public void onSaveWebpage(int saveType, DialogFragment dialogFragment) {
3017 Dialog dialog = dialogFragment.getDialog();
3019 // Remove the incorrect lint warning below that the dialog might be null.
3020 assert dialog != null;
3022 // Get a handle for the edit texts.
3023 EditText urlEditText = dialog.findViewById(R.id.url_edittext);
3024 EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
3026 // Get the strings from the edit texts.
3027 saveWebpageUrl = urlEditText.getText().toString();
3028 saveWebpageFilePath = fileNameEditText.getText().toString();
3030 // Check to see if the storage permission is needed.
3031 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
3032 //Save the webpage according to the save type.
3034 case StoragePermissionDialog.SAVE_URL:
3036 new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
3039 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3040 // Save the webpage archive.
3041 currentWebView.saveWebArchive(saveWebpageFilePath);
3044 case StoragePermissionDialog.SAVE_AS_IMAGE:
3045 // Save the webpage image.
3046 new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
3049 } else { // The storage permission has not been granted.
3050 // Get the external private directory file.
3051 File externalPrivateDirectoryFile = getExternalFilesDir(null);
3053 // Remove the incorrect lint error below that the file might be null.
3054 assert externalPrivateDirectoryFile != null;
3056 // Get the external private directory string.
3057 String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
3059 // Check to see if the file path is in the external private directory.
3060 if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory.
3061 // Save the webpage according to the save type.
3063 case StoragePermissionDialog.SAVE_URL:
3065 new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
3068 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3069 // Save the webpage archive.
3070 currentWebView.saveWebArchive(saveWebpageFilePath);
3073 case StoragePermissionDialog.SAVE_AS_IMAGE:
3074 // Save the webpage image.
3075 new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
3078 } else { // The file path is in a public directory.
3079 // Check if the user has previously denied the storage permission.
3080 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3081 // Instantiate the storage permission alert dialog.
3082 DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(saveType);
3084 // Show the storage permission alert dialog. The permission will be requested when the dialog is closed.
3085 storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
3086 } else { // Show the permission request directly.
3088 case StoragePermissionDialog.SAVE_URL:
3089 // Request the write external storage permission. The URL will be saved when it finishes.
3090 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE);
3092 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3093 // Request the write external storage permission. The webpage archive will be saved when it finishes.
3094 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE);
3097 case StoragePermissionDialog.SAVE_AS_IMAGE:
3098 // Request the write external storage permission. The webpage image will be saved when it finishes.
3099 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE);
3108 public void onCloseStoragePermissionDialog(int requestType) {
3109 switch (requestType) {
3110 case StoragePermissionDialog.OPEN:
3111 // Request the write external storage permission. The file will be opened when it finishes.
3112 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE);
3115 case StoragePermissionDialog.SAVE_URL:
3116 // Request the write external storage permission. The URL will be saved when it finishes.
3117 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE);
3120 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3121 // Request the write external storage permission. The webpage archive will be saved when it finishes.
3122 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE);
3125 case StoragePermissionDialog.SAVE_AS_IMAGE:
3126 // Request the write external storage permission. The webpage image will be saved when it finishes.
3127 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE);
3133 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3134 //Only process the results if they exist (this method is triggered when a dialog is presented the first time for an app, but no grant results are included).
3135 if (grantResults.length > 0) {
3136 switch (requestCode) {
3137 case PERMISSION_OPEN_REQUEST_CODE:
3138 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3139 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3141 currentWebView.loadUrl("file://" + openFilePath);
3142 } else { // The storage permission was not granted.
3143 // Display an error snackbar.
3144 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3147 // Reset the open file path.
3151 case PERMISSION_SAVE_URL_REQUEST_CODE:
3152 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3153 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3154 // Save the raw URL.
3155 new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
3156 } else { // The storage permission was not granted.
3157 // Display an error snackbar.
3158 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3161 // Reset the save strings.
3162 saveWebpageUrl = "";
3163 saveWebpageFilePath = "";
3166 case PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE:
3167 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3168 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3169 // Save the webpage archive.
3170 currentWebView.saveWebArchive(saveWebpageFilePath);
3171 } else { // The storage permission was not granted.
3172 // Display an error snackbar.
3173 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3176 // Reset the save webpage file path.
3177 saveWebpageFilePath = "";
3180 case PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE:
3181 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3182 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3183 // Save the webpage image.
3184 new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
3185 } else { // The storage permission was not granted.
3186 // Display an error snackbar.
3187 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3190 // Reset the save webpage file path.
3191 saveWebpageFilePath = "";
3197 private void applyAppSettings() {
3198 // 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.
3199 if (webViewDefaultUserAgent == null) {
3203 // Get a handle for the shared preferences.
3204 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3206 // Store the values from the shared preferences in variables.
3207 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
3208 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
3209 sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
3210 sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
3211 sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
3212 proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
3213 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
3214 hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
3215 scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
3217 // Get the search string.
3218 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
3220 // Set the search string.
3221 if (searchString.equals("Custom URL")) { // A custom search string is used.
3222 searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
3223 } else { // A custom search string is not used.
3224 searchURL = searchString;
3227 // Get handles for the views that need to be modified.
3228 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
3229 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
3230 ActionBar actionBar = getSupportActionBar();
3231 Toolbar toolbar = findViewById(R.id.toolbar);
3232 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
3233 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
3234 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
3236 // Remove the incorrect lint warning below that the action bar might be null.
3237 assert actionBar != null;
3242 // Set Do Not Track status.
3243 if (doNotTrackEnabled) {
3244 customHeaders.put("DNT", "1");
3246 customHeaders.remove("DNT");
3249 // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
3250 CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
3251 AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
3252 AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
3253 AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
3255 // Add the scrolling behavior to the layout parameters.
3257 // Enable scrolling of the app bar.
3258 swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
3259 toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3260 findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3261 tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3263 // Disable scrolling of the app bar.
3264 swipeRefreshLayoutParams.setBehavior(null);
3265 toolbarLayoutParams.setScrollFlags(0);
3266 findOnPageLayoutParams.setScrollFlags(0);
3267 tabsLayoutParams.setScrollFlags(0);
3269 // Expand the app bar if it is currently collapsed.
3270 appBarLayout.setExpanded(true);
3273 // Apply the modified layout parameters.
3274 swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
3275 toolbar.setLayoutParams(toolbarLayoutParams);
3276 findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
3277 tabsLinearLayout.setLayoutParams(tabsLayoutParams);
3279 // Set the app bar scrolling for each WebView.
3280 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3281 // Get the WebView tab fragment.
3282 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3284 // Get the fragment view.
3285 View fragmentView = webViewTabFragment.getView();
3287 // Only modify the WebViews if they exist.
3288 if (fragmentView != null) {
3289 // Get the nested scroll WebView from the tab fragment.
3290 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3292 // Set the app bar scrolling.
3293 nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
3297 // Update the full screen browsing mode settings.
3298 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
3299 // Update the visibility of the app bar, which might have changed in the settings.
3301 // Hide the tab linear layout.
3302 tabsLinearLayout.setVisibility(View.GONE);
3304 // Hide the action bar.
3307 // Show the tab linear layout.
3308 tabsLinearLayout.setVisibility(View.VISIBLE);
3310 // Show the action bar.
3314 // Hide the banner ad in the free flavor.
3315 if (BuildConfig.FLAVOR.contentEquals("free")) {
3316 AdHelper.hideAd(findViewById(R.id.adview));
3319 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
3320 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
3322 /* Hide the system bars.
3323 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
3324 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
3325 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
3326 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
3328 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
3329 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
3330 } else { // Privacy Browser is not in full screen browsing mode.
3331 // 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.
3332 inFullScreenBrowsingMode = false;
3334 // Show the tab linear layout.
3335 tabsLinearLayout.setVisibility(View.VISIBLE);
3337 // Show the action bar.
3340 // Show the banner ad in the free flavor.
3341 if (BuildConfig.FLAVOR.contentEquals("free")) {
3342 // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead.
3343 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
3346 // Remove the `SYSTEM_UI` flags from the root frame layout.
3347 rootFrameLayout.setSystemUiVisibility(0);
3349 // Add the translucent status flag.
3350 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
3354 private void initializeApp() {
3355 // Get a handle for the shared preferences.
3356 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3358 // Get the theme preference.
3359 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
3361 // Get a handle for the input method.
3362 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
3364 // Remove the lint warning below that the input method manager might be null.
3365 assert inputMethodManager != null;
3367 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
3368 redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
3369 initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
3370 finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
3372 // Get handles for the URL views.
3373 EditText urlEditText = findViewById(R.id.url_edittext);
3375 // Remove the formatting from the URL edit text when the user is editing the text.
3376 urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
3377 if (hasFocus) { // The user is editing the URL text box.
3378 // Remove the highlighting.
3379 urlEditText.getText().removeSpan(redColorSpan);
3380 urlEditText.getText().removeSpan(initialGrayColorSpan);
3381 urlEditText.getText().removeSpan(finalGrayColorSpan);
3382 } else { // The user has stopped editing the URL text box.
3383 // Move to the beginning of the string.
3384 urlEditText.setSelection(0);
3386 // Reapply the highlighting.
3391 // Set the go button on the keyboard to load the URL in `urlTextBox`.
3392 urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
3393 // If the event is a key-down event on the `enter` button, load the URL.
3394 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
3395 // Load the URL into the mainWebView and consume the event.
3396 loadUrlFromTextBox();
3398 // If the enter key was pressed, consume the event.
3401 // If any other key was pressed, do not consume the event.
3406 // Create an Orbot status broadcast receiver.
3407 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
3409 public void onReceive(Context context, Intent intent) {
3410 // Store the content of the status message in `orbotStatus`.
3411 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
3413 // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
3414 if ((orbotStatus != null) && orbotStatus.equals("ON") && waitingForProxy) {
3415 // Reset the waiting for proxy status.
3416 waitingForProxy = false;
3418 // Get a handle for the waiting for proxy dialog.
3419 DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog));
3421 // Dismiss the waiting for proxy dialog if it is displayed.
3422 if (waitingForProxyDialogFragment != null) {
3423 waitingForProxyDialogFragment.dismiss();
3426 // Reload existing URLs and load any URLs that are waiting for the proxy.
3427 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3428 // Get the WebView tab fragment.
3429 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3431 // Get the fragment view.
3432 View fragmentView = webViewTabFragment.getView();
3434 // Only process the WebViews if they exist.
3435 if (fragmentView != null) {
3436 // Get the nested scroll WebView from the tab fragment.
3437 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3439 // Get the waiting for proxy URL string.
3440 String waitingForProxyUrlString = nestedScrollWebView.getWaitingForProxyUrlString();
3442 // Load the pending URL if it exists.
3443 if (!waitingForProxyUrlString.isEmpty()) { // A URL is waiting to be loaded.
3445 loadUrl(nestedScrollWebView, waitingForProxyUrlString);
3447 // Reset the waiting for proxy URL string.
3448 nestedScrollWebView.resetWaitingForProxyUrlString();
3449 } else { // No URL is waiting to be loaded.
3450 // Reload the existing URL.
3451 nestedScrollWebView.reload();
3459 // Register the Orbot status broadcast receiver on `this` context.
3460 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
3462 // Get handles for views that need to be modified.
3463 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
3464 NavigationView navigationView = findViewById(R.id.navigationview);
3465 TabLayout tabLayout = findViewById(R.id.tablayout);
3466 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
3467 ViewPager webViewPager = findViewById(R.id.webviewpager);
3468 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
3469 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
3470 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
3471 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
3472 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
3474 // Listen for touches on the navigation menu.
3475 navigationView.setNavigationItemSelectedListener(this);
3477 // Get handles for the navigation menu and the back and forward menu items. The menu is 0 based.
3478 Menu navigationMenu = navigationView.getMenu();
3479 MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
3480 MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
3481 MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
3482 MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
3484 // Update the web view pager every time a tab is modified.
3485 webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
3487 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
3492 public void onPageSelected(int position) {
3493 // Close the find on page bar if it is open.
3494 closeFindOnPage(null);
3496 // Set the current WebView.
3497 setCurrentWebView(position);
3499 // 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.
3500 if (tabLayout.getSelectedTabPosition() != position) {
3501 // Create a handler to select the tab.
3502 Handler selectTabHandler = new Handler();
3504 // Create a runnable to select the tab.
3505 Runnable selectTabRunnable = () -> {
3506 // Get a handle for the tab.
3507 TabLayout.Tab tab = tabLayout.getTabAt(position);
3509 // Assert that the tab is not null.
3516 // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.
3517 selectTabHandler.postDelayed(selectTabRunnable, 150);
3522 public void onPageScrollStateChanged(int state) {
3527 // Display the View SSL Certificate dialog when the currently selected tab is reselected.
3528 tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
3530 public void onTabSelected(TabLayout.Tab tab) {
3531 // Select the same page in the view pager.
3532 webViewPager.setCurrentItem(tab.getPosition());
3536 public void onTabUnselected(TabLayout.Tab tab) {
3541 public void onTabReselected(TabLayout.Tab tab) {
3542 // Instantiate the View SSL Certificate dialog.
3543 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
3545 // Display the View SSL Certificate dialog.
3546 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
3550 // 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.
3551 // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
3553 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
3554 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
3555 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
3556 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
3558 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
3559 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
3560 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
3561 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
3564 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
3565 launchBookmarksActivityFab.setOnClickListener(v -> {
3566 // Get a copy of the favorite icon bitmap.
3567 Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
3569 // Create a favorite icon byte array output stream.
3570 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3572 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3573 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3575 // Convert the favorite icon byte array stream to a byte array.
3576 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3578 // Create an intent to launch the bookmarks activity.
3579 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
3581 // Add the extra information to the intent.
3582 bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
3583 bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
3584 bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
3585 bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
3588 startActivity(bookmarksIntent);
3591 // Set the create new bookmark folder FAB to display an alert dialog.
3592 createBookmarkFolderFab.setOnClickListener(v -> {
3593 // Create a create bookmark folder dialog.
3594 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
3596 // Show the create bookmark folder dialog.
3597 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
3600 // Set the create new bookmark FAB to display an alert dialog.
3601 createBookmarkFab.setOnClickListener(view -> {
3602 // Instantiate the create bookmark dialog.
3603 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
3605 // Display the create bookmark dialog.
3606 createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
3609 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
3610 findOnPageEditText.addTextChangedListener(new TextWatcher() {
3612 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3617 public void onTextChanged(CharSequence s, int start, int before, int count) {
3622 public void afterTextChanged(Editable s) {
3623 // 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.
3624 if (currentWebView != null) {
3625 currentWebView.findAllAsync(findOnPageEditText.getText().toString());
3630 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
3631 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
3632 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
3633 // Hide the soft keyboard.
3634 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3636 // Consume the event.
3638 } else { // A different key was pressed.
3639 // Do not consume the event.
3644 // Implement swipe to refresh.
3645 swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
3647 // Store the default progress view offsets for use later in `initializeWebView()`.
3648 defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
3649 defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
3651 // Set the swipe to refresh color according to the theme.
3653 swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
3654 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
3656 swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
3659 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
3660 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
3661 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
3663 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
3664 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
3666 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
3667 currentBookmarksFolder = "";
3669 // Load the home folder, which is `""` in the database.
3670 loadBookmarksFolder();
3672 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
3673 // Convert the id from long to int to match the format of the bookmarks database.
3674 int databaseId = (int) id;
3676 // Get the bookmark cursor for this ID.
3677 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3679 // Move the bookmark cursor to the first row.
3680 bookmarkCursor.moveToFirst();
3682 // Act upon the bookmark according to the type.
3683 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
3684 // Store the new folder name in `currentBookmarksFolder`.
3685 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3687 // Load the new folder.
3688 loadBookmarksFolder();
3689 } else { // The selected bookmark is not a folder.
3690 // Load the bookmark URL.
3691 loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
3693 // Close the bookmarks drawer.
3694 drawerLayout.closeDrawer(GravityCompat.END);
3697 // Close the `Cursor`.
3698 bookmarkCursor.close();
3701 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
3702 // Convert the database ID from `long` to `int`.
3703 int databaseId = (int) id;
3705 // Find out if the selected bookmark is a folder.
3706 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
3709 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
3710 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3712 // Instantiate the edit folder bookmark dialog.
3713 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
3715 // Show the edit folder bookmark dialog.
3716 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
3718 // Get the bookmark cursor for this ID.
3719 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3721 // Move the bookmark cursor to the first row.
3722 bookmarkCursor.moveToFirst();
3724 // Load the bookmark in a new tab but do not switch to the tab or close the drawer.
3725 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
3728 // Consume the event.
3732 // Get the status bar pixel size.
3733 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3734 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3736 // Get the resource density.
3737 float screenDensity = getResources().getDisplayMetrics().density;
3739 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
3740 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3741 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3742 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3744 // The drawer listener is used to update the navigation menu.
3745 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
3747 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
3751 public void onDrawerOpened(@NonNull View drawerView) {
3755 public void onDrawerClosed(@NonNull View drawerView) {
3759 public void onDrawerStateChanged(int newState) {
3760 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
3761 // Get handles for the drawer headers.
3762 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
3763 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
3765 // 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.
3766 if (navigationHeaderTextView != null) {
3767 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
3770 // 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.
3771 if (bookmarksHeaderTextView != null) {
3772 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
3775 // Update the navigation menu items if the WebView is not null.
3776 if (currentWebView != null) {
3777 navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
3778 navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
3779 navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
3780 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
3782 // Hide the keyboard (if displayed).
3783 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3786 // 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.
3787 urlEditText.clearFocus();
3788 currentWebView.clearFocus();
3793 // 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).
3794 customHeaders.put("X-Requested-With", "");
3796 // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen.
3797 @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
3799 // Get a handle for the WebView.
3800 WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
3802 // Store the default user agent.
3803 webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
3805 // Destroy the bare WebView.
3806 bareWebView.destroy();
3810 public void navigateHistory(String url, int steps) {
3811 // Apply the domain settings.
3812 applyDomainSettings(currentWebView, url, false, false);
3814 // Load the history entry.
3815 currentWebView.goBackOrForward(steps);
3819 public void pinnedErrorGoBack() {
3820 // Get the current web back forward list.
3821 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
3823 // Get the previous entry URL.
3824 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
3826 // Apply the domain settings.
3827 applyDomainSettings(currentWebView, previousUrl, false, false);
3830 currentWebView.goBack();
3833 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
3834 @SuppressLint("SetJavaScriptEnabled")
3835 private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
3836 // Store a copy of the current user agent to track changes for the return boolean.
3837 String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
3839 // Store the current URL.
3840 nestedScrollWebView.setCurrentUrl(url);
3842 // Parse the URL into a URI.
3843 Uri uri = Uri.parse(url);
3845 // Extract the domain from `uri`.
3846 String newHostName = uri.getHost();
3848 // Strings don't like to be null.
3849 if (newHostName == null) {
3853 // 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.
3854 if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) {
3855 // Set the new host name as the current domain name.
3856 nestedScrollWebView.setCurrentDomainName(newHostName);
3858 // Reset the ignoring of pinned domain information.
3859 nestedScrollWebView.setIgnorePinnedDomainInformation(false);
3861 // Clear any pinned SSL certificate or IP addresses.
3862 nestedScrollWebView.clearPinnedSslCertificate();
3863 nestedScrollWebView.clearPinnedIpAddresses();
3865 // Reset the favorite icon if specified.
3867 // Initialize the favorite icon.
3868 nestedScrollWebView.initializeFavoriteIcon();
3870 // Get the current page position.
3871 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
3873 // Get a handle for the tab layout.
3874 TabLayout tabLayout = findViewById(R.id.tablayout);
3876 // Get the corresponding tab.
3877 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
3879 // Update the tab if it isn't null, which sometimes happens when restarting from the background.
3881 // Get the tab custom view.
3882 View tabCustomView = tab.getCustomView();
3884 // Remove the warning below that the tab custom view might be null.
3885 assert tabCustomView != null;
3887 // Get the tab views.
3888 ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
3889 TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
3891 // Set the default favorite icon as the favorite icon for this tab.
3892 tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
3894 // Set the loading title text.
3895 tabTitleTextView.setText(R.string.loading);
3899 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
3900 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
3902 // Get a full cursor from `domainsDatabaseHelper`.
3903 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
3905 // Initialize `domainSettingsSet`.
3906 Set<String> domainSettingsSet = new HashSet<>();
3908 // Get the domain name column index.
3909 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
3911 // Populate `domainSettingsSet`.
3912 for (int i = 0; i < domainNameCursor.getCount(); i++) {
3913 // Move `domainsCursor` to the current row.
3914 domainNameCursor.moveToPosition(i);
3916 // Store the domain name in `domainSettingsSet`.
3917 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
3920 // Close `domainNameCursor.
3921 domainNameCursor.close();
3923 // Initialize the domain name in database variable.
3924 String domainNameInDatabase = null;
3926 // Check the hostname against the domain settings set.
3927 if (domainSettingsSet.contains(newHostName)) { // The hostname is contained in the domain settings set.
3928 // Record the domain name in the database.
3929 domainNameInDatabase = newHostName;
3931 // Set the domain settings applied tracker to true.
3932 nestedScrollWebView.setDomainSettingsApplied(true);
3933 } else { // The hostname is not contained in the domain settings set.
3934 // Set the domain settings applied tracker to false.
3935 nestedScrollWebView.setDomainSettingsApplied(false);
3938 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
3939 while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
3940 if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`.
3941 // Set the domain settings applied tracker to true.
3942 nestedScrollWebView.setDomainSettingsApplied(true);
3944 // Store the applied domain names as it appears in the database.
3945 domainNameInDatabase = "*." + newHostName;
3948 // Strip out the lowest subdomain of of the host name.
3949 newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
3953 // Get a handle for the shared preferences.
3954 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3956 // Store the general preference information.
3957 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
3958 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
3959 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
3960 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
3961 boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
3962 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
3964 // Get a handle for the cookie manager.
3965 CookieManager cookieManager = CookieManager.getInstance();
3967 // Get handles for the views.
3968 RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
3969 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
3971 // Initialize the user agent array adapter and string array.
3972 ArrayAdapter<CharSequence> userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
3973 String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
3975 if (nestedScrollWebView.getDomainSettingsApplied()) { // The url has custom domain settings.
3976 // Get a cursor for the current host and move it to the first position.
3977 Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
3978 currentDomainSettingsCursor.moveToFirst();
3980 // Get the settings from the cursor.
3981 nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
3982 nestedScrollWebView.setDomainSettingsJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
3983 nestedScrollWebView.setAcceptFirstPartyCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
3984 boolean domainThirdPartyCookiesEnabled = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
3985 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
3986 // Form data can be removed once the minimum API >= 26.
3987 boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
3988 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST,
3989 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
3990 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY,
3991 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
3992 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
3993 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
3994 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
3995 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
3996 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1);
3997 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY,
3998 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
3999 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
4000 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
4001 String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4002 int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4003 int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4004 int nightModeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4005 int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT));
4006 int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4007 boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4008 String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4009 String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4010 String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4011 String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4012 String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4013 String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4014 boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
4015 String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
4017 // Create the pinned SSL date variables.
4018 Date pinnedSslStartDate;
4019 Date pinnedSslEndDate;
4021 // 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.
4022 if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4023 pinnedSslStartDate = null;
4025 pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4028 // 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.
4029 if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4030 pinnedSslEndDate = null;
4032 pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4035 // If there is a pinned SSL certificate, store it in the WebView.
4036 if (pinnedSslCertificate) {
4037 nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
4038 pinnedSslStartDate, pinnedSslEndDate);
4041 // If there is a pinned IP address, store it in the WebView.
4042 if (pinnedIpAddresses) {
4043 nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses);
4046 // Set night mode according to the night mode int.
4047 switch (nightModeInt) {
4048 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4049 // Set night mode according to the current default.
4050 nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
4053 case DomainsDatabaseHelper.ENABLED:
4054 // Enable night mode.
4055 nestedScrollWebView.setNightMode(true);
4058 case DomainsDatabaseHelper.DISABLED:
4059 // Disable night mode.
4060 nestedScrollWebView.setNightMode(false);
4064 // Enable JavaScript if night mode is enabled.
4065 if (nestedScrollWebView.getNightMode()) {
4066 // Enable JavaScript.
4067 nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
4069 // Set JavaScript according to the domain settings.
4070 nestedScrollWebView.getSettings().setJavaScriptEnabled(nestedScrollWebView.getDomainSettingsJavaScriptEnabled());
4073 // Close the current host domain settings cursor.
4074 currentDomainSettingsCursor.close();
4076 // Apply the domain settings.
4077 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
4079 // Set third-party cookies status if API >= 21.
4080 if (Build.VERSION.SDK_INT >= 21) {
4081 cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, domainThirdPartyCookiesEnabled);
4084 // Apply the form data setting if the API < 26.
4085 if (Build.VERSION.SDK_INT < 26) {
4086 nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
4089 // Apply the font size.
4090 try { // Try the specified font size to see if it is valid.
4091 if (fontSize == 0) { // Apply the default font size.
4092 // Try to set the font size from the value in the app settings.
4093 nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
4094 } else { // Apply the font size from domain settings.
4095 nestedScrollWebView.getSettings().setTextZoom(fontSize);
4097 } catch (Exception exception) { // The specified font size is invalid
4098 // Set the font size to be 100%
4099 nestedScrollWebView.getSettings().setTextZoom(100);
4102 // Set the user agent.
4103 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4104 // Get the array position of the default user agent name.
4105 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4107 // Set the user agent according to the system default.
4108 switch (defaultUserAgentArrayPosition) {
4109 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4110 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4111 nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
4114 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4115 // Set the user agent to `""`, which uses the default value.
4116 nestedScrollWebView.getSettings().setUserAgentString("");
4119 case SETTINGS_CUSTOM_USER_AGENT:
4120 // Set the default custom user agent.
4121 nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
4125 // Get the user agent string from the user agent data array
4126 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4128 } else { // Set the user agent according to the stored name.
4129 // Get the array position of the user agent name.
4130 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4132 switch (userAgentArrayPosition) {
4133 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4134 nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
4137 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4138 // Set the user agent to `""`, which uses the default value.
4139 nestedScrollWebView.getSettings().setUserAgentString("");
4143 // Get the user agent string from the user agent data array.
4144 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4148 // Set swipe to refresh.
4149 switch (swipeToRefreshInt) {
4150 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4151 // Store the swipe to refresh status in the nested scroll WebView.
4152 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
4154 // 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.
4155 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4158 case DomainsDatabaseHelper.ENABLED:
4159 // Store the swipe to refresh status in the nested scroll WebView.
4160 nestedScrollWebView.setSwipeToRefresh(true);
4162 // Enable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
4163 swipeRefreshLayout.setEnabled(true);
4166 case DomainsDatabaseHelper.DISABLED:
4167 // Store the swipe to refresh status in the nested scroll WebView.
4168 nestedScrollWebView.setSwipeToRefresh(false);
4170 // Disable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
4171 swipeRefreshLayout.setEnabled(false);
4174 // Set the viewport.
4175 switch (wideViewportInt) {
4176 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4177 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4180 case DomainsDatabaseHelper.ENABLED:
4181 nestedScrollWebView.getSettings().setUseWideViewPort(true);
4184 case DomainsDatabaseHelper.DISABLED:
4185 nestedScrollWebView.getSettings().setUseWideViewPort(false);
4189 // Set the loading of webpage images.
4190 switch (displayWebpageImagesInt) {
4191 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4192 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4195 case DomainsDatabaseHelper.ENABLED:
4196 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true);
4199 case DomainsDatabaseHelper.DISABLED:
4200 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false);
4204 // 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.
4206 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4208 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4210 } else { // The new URL does not have custom domain settings. Load the defaults.
4211 // Store the values from the shared preferences.
4212 boolean defaultJavaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4213 nestedScrollWebView.setAcceptFirstPartyCookies(sharedPreferences.getBoolean("first_party_cookies", false));
4214 boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4215 nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
4216 boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4217 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true));
4218 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
4219 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
4220 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
4221 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true));
4222 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
4223 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
4224 nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
4226 // Enable JavaScript if night mode is enabled.
4227 if (nestedScrollWebView.getNightMode()) {
4228 // Enable JavaScript.
4229 nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
4231 // Set JavaScript according to the domain settings.
4232 nestedScrollWebView.getSettings().setJavaScriptEnabled(defaultJavaScriptEnabled);
4235 // Apply the default first-party cookie setting.
4236 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
4238 // Apply the default font size setting.
4240 // Try to set the font size from the value in the app settings.
4241 nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
4242 } catch (Exception exception) {
4243 // If the app settings value is invalid, set the font size to 100%.
4244 nestedScrollWebView.getSettings().setTextZoom(100);
4247 // Apply the form data setting if the API < 26.
4248 if (Build.VERSION.SDK_INT < 26) {
4249 nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
4252 // Store the swipe to refresh status in the nested scroll WebView.
4253 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
4255 // Apply swipe to refresh according to the default.
4256 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4258 // Reset the pinned variables.
4259 nestedScrollWebView.setDomainSettingsDatabaseId(-1);
4261 // Set third-party cookies status if API >= 21.
4262 if (Build.VERSION.SDK_INT >= 21) {
4263 cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled);
4266 // Get the array position of the user agent name.
4267 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4269 // Set the user agent.
4270 switch (userAgentArrayPosition) {
4271 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4272 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4273 nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
4276 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4277 // Set the user agent to `""`, which uses the default value.
4278 nestedScrollWebView.getSettings().setUserAgentString("");
4281 case SETTINGS_CUSTOM_USER_AGENT:
4282 // Set the default custom user agent.
4283 nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
4287 // Get the user agent string from the user agent data array
4288 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4291 // Set the viewport.
4292 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4294 // Set the loading of webpage images.
4295 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4297 // Set a transparent background on URL edit text. The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21.
4298 urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
4301 // Close the domains database helper.
4302 domainsDatabaseHelper.close();
4304 // Update the privacy icons.
4305 updatePrivacyIcons(true);
4308 // Reload the website if returning from the Domains activity.
4309 if (reloadWebsite) {
4310 nestedScrollWebView.reload();
4313 // Return the user agent changed status.
4314 return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent);
4317 private void applyProxy(boolean reloadWebViews) {
4318 // Get a handle for the shared preferences.
4319 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4321 // Get the theme preferences.
4322 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
4324 // Get a handle for the app bar layout.
4325 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
4327 // Set the proxy according to the mode. `this` refers to the current activity where an alert dialog might be displayed.
4328 ProxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode);
4330 // Reset the waiting for proxy tracker.
4331 waitingForProxy = false;
4333 // Update the user interface and reload the WebViews if requested.
4334 switch (proxyMode) {
4335 case ProxyHelper.NONE:
4336 // Set the default app bar layout background.
4338 appBarLayout.setBackgroundResource(R.color.gray_900);
4340 appBarLayout.setBackgroundResource(R.color.gray_100);
4344 case ProxyHelper.TOR:
4345 // Set the app bar background to indicate proxying through Orbot is enabled.
4347 appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4349 appBarLayout.setBackgroundResource(R.color.blue_50);
4352 // Check to see if Orbot is installed.
4354 // Get the package manager.
4355 PackageManager packageManager = getPackageManager();
4357 // Check to see if Orbot is in the list. This will throw an error and drop to the catch section if it isn't installed.
4358 packageManager.getPackageInfo("org.torproject.android", 0);
4360 // Check to see if the proxy is ready.
4361 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4362 // Set the waiting for proxy status.
4363 waitingForProxy = true;
4365 // Show the waiting for proxy dialog if it isn't already displayed.
4366 if (getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) {
4367 // Get a handle for the waiting for proxy alert dialog.
4368 DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog();
4370 // Display the waiting for proxy alert dialog.
4371 waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
4374 } catch (PackageManager.NameNotFoundException exception) { // Orbot is not installed.
4375 // Show the Orbot not installed dialog if it is not already displayed.
4376 if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4377 // Get a handle for the Orbot not installed alert dialog.
4378 DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4380 // Display the Orbot not installed alert dialog.
4381 orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4386 case ProxyHelper.I2P:
4387 // Set the app bar background to indicate proxying through Orbot is enabled.
4389 appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4391 appBarLayout.setBackgroundResource(R.color.blue_50);
4394 // Check to see if I2P is installed.
4396 // Get the package manager.
4397 PackageManager packageManager = getPackageManager();
4399 // Check to see if I2P is in the list. This will throw an error and drop to the catch section if it isn't installed.
4400 packageManager.getPackageInfo("org.torproject.android", 0);
4401 } catch (PackageManager.NameNotFoundException exception) { // I2P is not installed.
4402 // Sow the I2P not installed dialog if it is not already displayed.
4403 if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4404 // Get a handle for the waiting for proxy alert dialog.
4405 DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4407 // Display the I2P not installed alert dialog.
4408 i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4413 case ProxyHelper.CUSTOM:
4414 // Set the app bar background to indicate proxying through Orbot is enabled.
4416 appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4418 appBarLayout.setBackgroundResource(R.color.blue_50);
4423 // Reload the WebViews if requested and not waiting for the proxy.
4424 if (reloadWebViews && !waitingForProxy) {
4425 // Reload the WebViews.
4426 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4427 // Get the WebView tab fragment.
4428 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4430 // Get the fragment view.
4431 View fragmentView = webViewTabFragment.getView();
4433 // Only reload the WebViews if they exist.
4434 if (fragmentView != null) {
4435 // Get the nested scroll WebView from the tab fragment.
4436 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4438 // Reload the WebView.
4439 nestedScrollWebView.reload();
4445 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4446 // Only update the privacy icons if the options menu and the current WebView have already been populated.
4447 if ((optionsMenu != null) && (currentWebView != null)) {
4448 // Get a handle for the shared preferences.
4449 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4451 // Get the theme and screenshot preferences.
4452 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
4454 // Get handles for the menu items.
4455 MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript);
4456 MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies);
4457 MenuItem domStorageMenuItem = optionsMenu.findItem(R.id.toggle_dom_storage);
4458 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
4460 // Update the privacy icon.
4461 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled.
4462 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4463 } else if (currentWebView.getAcceptFirstPartyCookies()) { // JavaScript is disabled but cookies are enabled.
4464 privacyMenuItem.setIcon(R.drawable.warning);
4465 } else { // All the dangerous features are disabled.
4466 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4469 // Update the first-party cookies icon.
4470 if (currentWebView.getAcceptFirstPartyCookies()) { // First-party cookies are enabled.
4471 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4472 } else { // First-party cookies are disabled.
4474 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4476 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4480 // Update the DOM storage icon.
4481 if (currentWebView.getSettings().getJavaScriptEnabled() && currentWebView.getSettings().getDomStorageEnabled()) { // Both JavaScript and DOM storage are enabled.
4482 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4483 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled but DOM storage is disabled.
4485 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4487 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4489 } else { // JavaScript is disabled, so DOM storage is ghosted.
4491 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4493 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4497 // Update the refresh icon.
4499 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4501 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4504 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4505 if (runInvalidateOptionsMenu) {
4506 invalidateOptionsMenu();
4511 private void highlightUrlText() {
4512 // Get a handle for the URL edit text.
4513 EditText urlEditText = findViewById(R.id.url_edittext);
4515 // Only highlight the URL text if the box is not currently selected.
4516 if (!urlEditText.hasFocus()) {
4517 // Get the URL string.
4518 String urlString = urlEditText.getText().toString();
4520 // Highlight the URL according to the protocol.
4521 if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL.
4522 // De-emphasize everything before the file name.
4523 urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4524 } else { // This is a web URL.
4525 // Get the index of the `/` immediately after the domain name.
4526 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4528 // Create a base URL string.
4531 // Get the base URL.
4532 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4533 // Get the base URL.
4534 baseUrl = urlString.substring(0, endOfDomainName);
4535 } else { // There are no characters after the base URL.
4536 // Set the base URL to be the entire URL string.
4537 baseUrl = urlString;
4540 // Get the index of the last `.` in the domain.
4541 int lastDotIndex = baseUrl.lastIndexOf(".");
4543 // Get the index of the penultimate `.` in the domain.
4544 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4546 // Markup the beginning of the URL.
4547 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4548 urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4550 // De-emphasize subdomains.
4551 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4552 urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4554 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4555 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4556 // De-emphasize the protocol and the additional subdomains.
4557 urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4558 } else { // There is only one subdomain in the domain name.
4559 // De-emphasize only the protocol.
4560 urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4564 // De-emphasize the text after the domain name.
4565 if (endOfDomainName > 0) {
4566 urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4572 private void loadBookmarksFolder() {
4573 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4574 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4576 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4577 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4579 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4580 // Inflate the individual item layout. `false` does not attach it to the root.
4581 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4585 public void bindView(View view, Context context, Cursor cursor) {
4586 // Get handles for the views.
4587 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4588 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4590 // Get the favorite icon byte array from the cursor.
4591 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4593 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4594 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4596 // Display the bitmap in `bookmarkFavoriteIcon`.
4597 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4599 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4600 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4601 bookmarkNameTextView.setText(bookmarkNameString);
4603 // Make the font bold for folders.
4604 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4605 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4606 } else { // Reset the font to default for normal bookmarks.
4607 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4612 // Get a handle for the bookmarks list view.
4613 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
4615 // Populate the list view with the adapter.
4616 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4618 // Get a handle for the bookmarks title text view.
4619 TextView bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
4621 // Set the bookmarks drawer title.
4622 if (currentBookmarksFolder.isEmpty()) {
4623 bookmarksTitleTextView.setText(R.string.bookmarks);
4625 bookmarksTitleTextView.setText(currentBookmarksFolder);
4629 private void openWithApp(String url) {
4630 // Create an open with app intent with `ACTION_VIEW`.
4631 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4633 // Set the URI but not the MIME type. This should open all available apps.
4634 openWithAppIntent.setData(Uri.parse(url));
4636 // Flag the intent to open in a new task.
4637 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4641 // Show the chooser.
4642 startActivity(openWithAppIntent);
4643 } catch (ActivityNotFoundException exception) { // There are no apps available to open the URL.
4644 // Show a snackbar with the error.
4645 Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
4649 private void openWithBrowser(String url) {
4650 // Create an open with browser intent with `ACTION_VIEW`.
4651 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4653 // Set the URI and the MIME type. `"text/html"` should load browser options.
4654 openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4656 // Flag the intent to open in a new task.
4657 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4661 // Show the chooser.
4662 startActivity(openWithBrowserIntent);
4663 } catch (ActivityNotFoundException exception) { // There are no browsers available to open the URL.
4664 // Show a snackbar with the error.
4665 Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
4669 private String sanitizeUrl(String url) {
4670 // Sanitize Google Analytics.
4671 if (sanitizeGoogleAnalytics) {
4673 if (url.contains("?utm_")) {
4674 url = url.substring(0, url.indexOf("?utm_"));
4678 if (url.contains("&utm_")) {
4679 url = url.substring(0, url.indexOf("&utm_"));
4683 // Sanitize Facebook Click IDs.
4684 if (sanitizeFacebookClickIds) {
4685 // Remove `?fbclid=`.
4686 if (url.contains("?fbclid=")) {
4687 url = url.substring(0, url.indexOf("?fbclid="));
4690 // Remove `&fbclid=`.
4691 if (url.contains("&fbclid=")) {
4692 url = url.substring(0, url.indexOf("&fbclid="));
4695 // Remove `?fbadid=`.
4696 if (url.contains("?fbadid=")) {
4697 url = url.substring(0, url.indexOf("?fbadid="));
4700 // Remove `&fbadid=`.
4701 if (url.contains("&fbadid=")) {
4702 url = url.substring(0, url.indexOf("&fbadid="));
4706 // Sanitize Twitter AMP redirects.
4707 if (sanitizeTwitterAmpRedirects) {
4709 if (url.contains("?amp=1")) {
4710 url = url.substring(0, url.indexOf("?amp=1"));
4714 // Return the sanitized URL.
4718 public void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
4719 // Store the blocklists.
4720 easyList = combinedBlocklists.get(0);
4721 easyPrivacy = combinedBlocklists.get(1);
4722 fanboysAnnoyanceList = combinedBlocklists.get(2);
4723 fanboysSocialList = combinedBlocklists.get(3);
4724 ultraList = combinedBlocklists.get(4);
4725 ultraPrivacy = combinedBlocklists.get(5);
4727 // Add the first tab.
4728 addNewTab("", true);
4731 public void addTab(View view) {
4732 // Add a new tab with a blank URL.
4733 addNewTab("", true);
4736 private void addNewTab(String url, boolean moveToTab) {
4737 // Sanitize the URL.
4738 url = sanitizeUrl(url);
4740 // Get a handle for the tab layout and the view pager.
4741 TabLayout tabLayout = findViewById(R.id.tablayout);
4742 ViewPager webViewPager = findViewById(R.id.webviewpager);
4744 // Get the new page number. The page numbers are 0 indexed, so the new page number will match the current count.
4745 int newTabNumber = tabLayout.getTabCount();
4748 tabLayout.addTab(tabLayout.newTab());
4751 TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber);
4753 // Remove the lint warning below that the current tab might be null.
4754 assert newTab != null;
4756 // Set a custom view on the new tab.
4757 newTab.setCustomView(R.layout.tab_custom_view);
4759 // Add the new WebView page.
4760 webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
4763 public void closeTab(View view) {
4764 // Get a handle for the tab layout.
4765 TabLayout tabLayout = findViewById(R.id.tablayout);
4767 // Run the command according to the number of tabs.
4768 if (tabLayout.getTabCount() > 1) { // There is more than one tab open.
4769 // Close the current tab.
4771 } else { // There is only one tab open.
4776 private void closeCurrentTab() {
4777 // Get handles for the views.
4778 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
4779 TabLayout tabLayout = findViewById(R.id.tablayout);
4780 ViewPager webViewPager = findViewById(R.id.webviewpager);
4782 // Get the current tab number.
4783 int currentTabNumber = tabLayout.getSelectedTabPosition();
4785 // Delete the current tab.
4786 tabLayout.removeTabAt(currentTabNumber);
4788 // 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.
4789 if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
4790 setCurrentWebView(currentTabNumber);
4793 // Expand the app bar if it is currently collapsed.
4794 appBarLayout.setExpanded(true);
4797 private void clearAndExit() {
4798 // Get a handle for the shared preferences.
4799 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4801 // Close the bookmarks cursor and database.
4802 bookmarksCursor.close();
4803 bookmarksDatabaseHelper.close();
4805 // Get the status of the clear everything preference.
4806 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
4808 // Get a handle for the runtime.
4809 Runtime runtime = Runtime.getRuntime();
4811 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
4812 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
4813 String privateDataDirectoryString = getApplicationInfo().dataDir;
4816 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
4817 // The command to remove cookies changed slightly in API 21.
4818 if (Build.VERSION.SDK_INT >= 21) {
4819 CookieManager.getInstance().removeAllCookies(null);
4821 CookieManager.getInstance().removeAllCookie();
4824 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4826 // Two commands must be used because `Runtime.exec()` does not like `*`.
4827 Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
4828 Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
4830 // Wait until the processes have finished.
4831 deleteCookiesProcess.waitFor();
4832 deleteCookiesJournalProcess.waitFor();
4833 } catch (Exception exception) {
4834 // Do nothing if an error is thrown.
4838 // Clear DOM storage.
4839 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
4840 // Ask `WebStorage` to clear the DOM storage.
4841 WebStorage webStorage = WebStorage.getInstance();
4842 webStorage.deleteAllData();
4844 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4846 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4847 Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
4849 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
4850 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
4851 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
4852 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
4853 Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
4855 // Wait until the processes have finished.
4856 deleteLocalStorageProcess.waitFor();
4857 deleteIndexProcess.waitFor();
4858 deleteQuotaManagerProcess.waitFor();
4859 deleteQuotaManagerJournalProcess.waitFor();
4860 deleteDatabaseProcess.waitFor();
4861 } catch (Exception exception) {
4862 // Do nothing if an error is thrown.
4866 // Clear form data if the API < 26.
4867 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
4868 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
4869 webViewDatabase.clearFormData();
4871 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4873 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
4874 Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
4875 Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
4877 // Wait until the processes have finished.
4878 deleteWebDataProcess.waitFor();
4879 deleteWebDataJournalProcess.waitFor();
4880 } catch (Exception exception) {
4881 // Do nothing if an error is thrown.
4886 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
4887 // Clear the cache from each WebView.
4888 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4889 // Get the WebView tab fragment.
4890 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4892 // Get the fragment view.
4893 View fragmentView = webViewTabFragment.getView();
4895 // Only clear the cache if the WebView exists.
4896 if (fragmentView != null) {
4897 // Get the nested scroll WebView from the tab fragment.
4898 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4900 // Clear the cache for this WebView.
4901 nestedScrollWebView.clearCache(true);
4905 // Manually delete the cache directories.
4907 // Delete the main cache directory.
4908 Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
4910 // Delete the secondary `Service Worker` cache directory.
4911 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4912 Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
4914 // Wait until the processes have finished.
4915 deleteCacheProcess.waitFor();
4916 deleteServiceWorkerProcess.waitFor();
4917 } catch (Exception exception) {
4918 // Do nothing if an error is thrown.
4922 // Wipe out each WebView.
4923 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4924 // Get the WebView tab fragment.
4925 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4927 // Get the fragment view.
4928 View fragmentView = webViewTabFragment.getView();
4930 // Only wipe out the WebView if it exists.
4931 if (fragmentView != null) {
4932 // Get the nested scroll WebView from the tab fragment.
4933 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4935 // Clear SSL certificate preferences for this WebView.
4936 nestedScrollWebView.clearSslPreferences();
4938 // Clear the back/forward history for this WebView.
4939 nestedScrollWebView.clearHistory();
4941 // Destroy the internal state of `mainWebView`.
4942 nestedScrollWebView.destroy();
4946 // Clear the custom headers.
4947 customHeaders.clear();
4949 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
4950 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
4951 if (clearEverything) {
4953 // Delete the folder.
4954 Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
4956 // Wait until the process has finished.
4957 deleteAppWebviewProcess.waitFor();
4958 } catch (Exception exception) {
4959 // Do nothing if an error is thrown.
4963 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
4964 if (Build.VERSION.SDK_INT >= 21) {
4965 finishAndRemoveTask();
4970 // Remove the terminated program from RAM. The status code is `0`.
4974 private void setCurrentWebView(int pageNumber) {
4975 // Get a handle for the shared preferences.
4976 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4978 // Get the theme preference.
4979 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
4981 // Get handles for the URL views.
4982 RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
4983 EditText urlEditText = findViewById(R.id.url_edittext);
4984 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
4986 // Stop the swipe to refresh indicator if it is running
4987 swipeRefreshLayout.setRefreshing(false);
4989 // Get the WebView tab fragment.
4990 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber);
4992 // Get the fragment view.
4993 View fragmentView = webViewTabFragment.getView();
4995 // Set the current WebView if the fragment view is not null.
4996 if (fragmentView != null) { // The fragment has been populated.
4997 // Store the current WebView.
4998 currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
5000 // Update the status of swipe to refresh.
5001 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
5002 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top. It is updated every time the scroll changes.
5003 swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
5004 } else { // Swipe to refresh is disabled.
5005 // Disable the swipe refresh layout.
5006 swipeRefreshLayout.setEnabled(false);
5009 // Get a handle for the cookie manager.
5010 CookieManager cookieManager = CookieManager.getInstance();
5012 // Set the first-party cookie status.
5013 cookieManager.setAcceptCookie(currentWebView.getAcceptFirstPartyCookies());
5015 // Update the privacy icons. `true` redraws the icons in the app bar.
5016 updatePrivacyIcons(true);
5018 // Get a handle for the input method manager.
5019 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5021 // Remove the lint warning below that the input method manager might be null.
5022 assert inputMethodManager != null;
5024 // Get the current URL.
5025 String url = currentWebView.getUrl();
5027 // Update the URL edit text if not loading a new intent. Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
5028 if (!loadingNewIntent) { // A new intent is not being loaded.
5029 if ((url == null) || url.equals("about:blank")) { // The WebView is blank.
5030 // Display the hint in the URL edit text.
5031 urlEditText.setText("");
5033 // Request focus for the URL text box.
5034 urlEditText.requestFocus();
5036 // Display the keyboard.
5037 inputMethodManager.showSoftInput(urlEditText, 0);
5038 } else { // The WebView has a loaded URL.
5039 // Clear the focus from the URL text box.
5040 urlEditText.clearFocus();
5042 // Hide the soft keyboard.
5043 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
5045 // Display the current URL in the URL text box.
5046 urlEditText.setText(url);
5048 // Highlight the URL text.
5051 } else { // A new intent is being loaded.
5052 // Reset the loading new intent tracker.
5053 loadingNewIntent = false;
5056 // Set the background to indicate the domain settings status.
5057 if (currentWebView.getDomainSettingsApplied()) {
5058 // 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.
5060 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
5062 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
5065 urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
5067 } else { // The fragment has not been populated. Try again in 100 milliseconds.
5068 // Create a handler to set the current WebView.
5069 Handler setCurrentWebViewHandler = new Handler();
5071 // Create a runnable to set the current WebView.
5072 Runnable setCurrentWebWebRunnable = () -> {
5073 // Set the current WebView.
5074 setCurrentWebView(pageNumber);
5077 // Try setting the current WebView again after 100 milliseconds.
5078 setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100);
5083 public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url) {
5084 // Get handles for the activity views.
5085 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
5086 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
5087 RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
5088 ActionBar actionBar = getSupportActionBar();
5089 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
5090 EditText urlEditText = findViewById(R.id.url_edittext);
5091 TabLayout tabLayout = findViewById(R.id.tablayout);
5092 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
5094 // Remove the incorrect lint warning below that the action bar might be null.
5095 assert actionBar != null;
5097 // Get a handle for the activity
5098 Activity activity = this;
5100 // Get a handle for the input method manager.
5101 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5103 // Instantiate the blocklist helper.
5104 BlocklistHelper blocklistHelper = new BlocklistHelper();
5106 // Remove the lint warning below that the input method manager might be null.
5107 assert inputMethodManager != null;
5109 // Get a handle for the shared preferences.
5110 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
5112 // Initialize the favorite icon.
5113 nestedScrollWebView.initializeFavoriteIcon();
5115 // Set the app bar scrolling.
5116 nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
5118 // Allow pinch to zoom.
5119 nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
5121 // Hide zoom controls.
5122 nestedScrollWebView.getSettings().setDisplayZoomControls(false);
5124 // Don't allow mixed content (HTTP and HTTPS) on the same website.
5125 if (Build.VERSION.SDK_INT >= 21) {
5126 nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
5129 // Set the WebView to load in overview mode (zoomed out to the maximum width).
5130 nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
5132 // Explicitly disable geolocation.
5133 nestedScrollWebView.getSettings().setGeolocationEnabled(false);
5135 // Create a double-tap gesture detector to toggle full-screen mode.
5136 GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
5137 // Override `onDoubleTap()`. All other events are handled using the default settings.
5139 public boolean onDoubleTap(MotionEvent event) {
5140 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
5141 // Toggle the full screen browsing mode tracker.
5142 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
5144 // Toggle the full screen browsing mode.
5145 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
5146 // Store the swipe refresh layout top padding.
5147 swipeRefreshLayoutPaddingTop = swipeRefreshLayout.getPaddingTop();
5149 // Hide the app bar if specified.
5151 // Close the find on page bar if it is visible.
5152 closeFindOnPage(null);
5154 // Hide the tab linear layout.
5155 tabsLinearLayout.setVisibility(View.GONE);
5157 // Hide the action bar.
5160 // Check to see if app bar scrolling is disabled.
5161 if (!scrollAppBar) {
5162 // Remove the padding from the top of the swipe refresh layout.
5163 swipeRefreshLayout.setPadding(0, 0, 0, 0);
5167 // Hide the banner ad in the free flavor.
5168 if (BuildConfig.FLAVOR.contentEquals("free")) {
5169 AdHelper.hideAd(findViewById(R.id.adview));
5172 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
5173 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5175 /* Hide the system bars.
5176 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5177 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5178 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5179 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5181 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5182 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5183 } else { // Switch to normal viewing mode.
5184 // Show the tab linear layout.
5185 tabsLinearLayout.setVisibility(View.VISIBLE);
5187 // Show the action bar.
5190 // Check to see if app bar scrolling is disabled.
5191 if (!scrollAppBar) {
5192 // Add the padding from the top of the swipe refresh layout.
5193 swipeRefreshLayout.setPadding(0, swipeRefreshLayoutPaddingTop, 0, 0);
5196 // Show the banner ad in the free flavor.
5197 if (BuildConfig.FLAVOR.contentEquals("free")) {
5199 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
5202 // Remove the `SYSTEM_UI` flags from the root frame layout.
5203 rootFrameLayout.setSystemUiVisibility(0);
5205 // Add the translucent status flag.
5206 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5209 // Consume the double-tap.
5211 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
5217 // Pass all touch events on the WebView through the double-tap gesture detector.
5218 nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
5219 // Call `performClick()` on the view, which is required for accessibility.
5220 view.performClick();
5222 // Send the event to the gesture detector.
5223 return doubleTapGestureDetector.onTouchEvent(event);
5226 // Register the WebView for a context menu. This is used to see link targets and download images.
5227 registerForContextMenu(nestedScrollWebView);
5229 // Allow the downloading of files.
5230 nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
5231 // Instantiate the save dialog.
5232 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, downloadUrl, userAgent, nestedScrollWebView.getAcceptFirstPartyCookies());
5234 // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
5235 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
5238 // Update the find on page count.
5239 nestedScrollWebView.setFindListener(new WebView.FindListener() {
5240 // Get a handle for `findOnPageCountTextView`.
5241 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
5244 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
5245 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
5246 // Set `findOnPageCountTextView` to `0/0`.
5247 findOnPageCountTextView.setText(R.string.zero_of_zero);
5248 } else if (isDoneCounting) { // There are matches.
5249 // `activeMatchOrdinal` is zero-based.
5250 int activeMatch = activeMatchOrdinal + 1;
5252 // Build the match string.
5253 String matchString = activeMatch + "/" + numberOfMatches;
5255 // Set `findOnPageCountTextView`.
5256 findOnPageCountTextView.setText(matchString);
5261 // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. Also reinforce full screen browsing mode.
5262 // Once the minimum API >= 23 this can be replaced with `nestedScrollWebView.setOnScrollChangeListener()`.
5263 nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
5264 if (nestedScrollWebView.getSwipeToRefresh()) {
5265 // Only enable swipe to refresh if the WebView is scrolled to the top.
5266 swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
5269 // Reinforce the system UI visibility flags if in full screen browsing mode.
5270 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
5271 if (inFullScreenBrowsingMode) {
5272 /* Hide the system bars.
5273 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5274 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5275 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5276 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5278 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5279 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5283 // Set the web chrome client.
5284 nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
5285 // Update the progress bar when a page is loading.
5287 public void onProgressChanged(WebView view, int progress) {
5288 // Inject the night mode CSS if night mode is enabled.
5289 if (nestedScrollWebView.getNightMode()) { // Night mode is enabled.
5290 // `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
5291 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
5292 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
5293 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
5294 nestedScrollWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
5295 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
5296 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
5297 // Initialize a handler to display `mainWebView`.
5298 Handler displayWebViewHandler = new Handler();
5300 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
5301 Runnable displayWebViewRunnable = () -> {
5302 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
5303 if (progressBar.getVisibility() == View.GONE) {
5304 nestedScrollWebView.setVisibility(View.VISIBLE);
5308 // Display the WebView after 500 milliseconds.
5309 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
5311 } else { // Night mode is disabled.
5312 // Display the nested scroll WebView if night mode is disabled.
5313 // Because of a race condition between `applyDomainSettings` and `onPageStarted`,
5314 // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled.
5315 nestedScrollWebView.setVisibility(View.VISIBLE);
5318 // Update the progress bar.
5319 progressBar.setProgress(progress);
5321 // Set the visibility of the progress bar.
5322 if (progress < 100) {
5323 // Show the progress bar.
5324 progressBar.setVisibility(View.VISIBLE);
5326 // Hide the progress bar.
5327 progressBar.setVisibility(View.GONE);
5329 //Stop the swipe to refresh indicator if it is running
5330 swipeRefreshLayout.setRefreshing(false);
5334 // Set the favorite icon when it changes.
5336 public void onReceivedIcon(WebView view, Bitmap icon) {
5337 // Only update the favorite icon if the website has finished loading.
5338 if (progressBar.getVisibility() == View.GONE) {
5339 // Store the new favorite icon.
5340 nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
5342 // Get the current page position.
5343 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5345 // Get the current tab.
5346 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5348 // Check to see if the tab has been populated.
5350 // Get the custom view from the tab.
5351 View tabView = tab.getCustomView();
5353 // Check to see if the custom tab view has been populated.
5354 if (tabView != null) {
5355 // Get the favorite icon image view from the tab.
5356 ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
5358 // Display the favorite icon in the tab.
5359 tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
5365 // Save a copy of the title when it changes.
5367 public void onReceivedTitle(WebView view, String title) {
5368 // Get the current page position.
5369 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5371 // Get the current tab.
5372 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5374 // Only populate the title text view if the tab has been fully created.
5376 // Get the custom view from the tab.
5377 View tabView = tab.getCustomView();
5379 // Remove the incorrect warning below that the current tab view might be null.
5380 assert tabView != null;
5382 // Get the title text view from the tab.
5383 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
5385 // Set the title according to the URL.
5386 if (title.equals("about:blank")) {
5387 // Set the title to indicate a new tab.
5388 tabTitleTextView.setText(R.string.new_tab);
5390 // Set the title as the tab text.
5391 tabTitleTextView.setText(title);
5396 // Enter full screen video.
5398 public void onShowCustomView(View video, CustomViewCallback callback) {
5399 // Get a handle for the full screen video frame layout.
5400 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
5402 // Set the full screen video flag.
5403 displayingFullScreenVideo = true;
5405 // Pause the ad if this is the free flavor.
5406 if (BuildConfig.FLAVOR.contentEquals("free")) {
5407 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
5408 AdHelper.pauseAd(findViewById(R.id.adview));
5411 // Hide the keyboard.
5412 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
5414 // Hide the main content relative layout.
5415 mainContentRelativeLayout.setVisibility(View.GONE);
5417 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
5418 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
5420 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
5421 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5423 /* Hide the system bars.
5424 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5425 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5426 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5427 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5429 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5430 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5432 // Disable the sliding drawers.
5433 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
5435 // Add the video view to the full screen video frame layout.
5436 fullScreenVideoFrameLayout.addView(video);
5438 // Show the full screen video frame layout.
5439 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
5441 // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do.
5442 fullScreenVideoFrameLayout.setKeepScreenOn(true);
5445 // Exit full screen video.
5447 public void onHideCustomView() {
5448 // Get a handle for the full screen video frame layout.
5449 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
5451 // Re-enable the screen timeout.
5452 fullScreenVideoFrameLayout.setKeepScreenOn(false);
5454 // Unset the full screen video flag.
5455 displayingFullScreenVideo = false;
5457 // Remove all the views from the full screen video frame layout.
5458 fullScreenVideoFrameLayout.removeAllViews();
5460 // Hide the full screen video frame layout.
5461 fullScreenVideoFrameLayout.setVisibility(View.GONE);
5463 // Enable the sliding drawers.
5464 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
5466 // Show the main content relative layout.
5467 mainContentRelativeLayout.setVisibility(View.VISIBLE);
5469 // Apply the appropriate full screen mode flags.
5470 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
5471 // Hide the app bar if specified.
5473 // Hide the tab linear layout.
5474 tabsLinearLayout.setVisibility(View.GONE);
5476 // Hide the action bar.
5480 // Hide the banner ad in the free flavor.
5481 if (BuildConfig.FLAVOR.contentEquals("free")) {
5482 AdHelper.hideAd(findViewById(R.id.adview));
5485 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
5486 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5488 /* Hide the system bars.
5489 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5490 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5491 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5492 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5494 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5495 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5496 } else { // Switch to normal viewing mode.
5497 // Remove the `SYSTEM_UI` flags from the root frame layout.
5498 rootFrameLayout.setSystemUiVisibility(0);
5500 // Add the translucent status flag.
5501 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5504 // Reload the ad for the free flavor if not in full screen mode.
5505 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
5507 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
5513 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
5514 // Show the file chooser if the device is running API >= 21.
5515 if (Build.VERSION.SDK_INT >= 21) {
5516 // Store the file path callback.
5517 fileChooserCallback = filePathCallback;
5519 // Create an intent to open a chooser based ont the file chooser parameters.
5520 Intent fileChooserIntent = fileChooserParams.createIntent();
5522 // Open the file chooser.
5523 startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
5529 nestedScrollWebView.setWebViewClient(new WebViewClient() {
5530 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
5531 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
5533 public boolean shouldOverrideUrlLoading(WebView view, String url) {
5534 // Sanitize the url.
5535 url = sanitizeUrl(url);
5537 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
5538 // Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed.
5539 boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
5541 // Check if the user agent has changed.
5542 if (userAgentChanged) {
5543 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
5544 nestedScrollWebView.loadUrl(url, customHeaders);
5546 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
5549 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
5552 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
5553 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
5554 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
5556 // Parse the url and set it as the data for the intent.
5557 emailIntent.setData(Uri.parse(url));
5559 // Open the email program in a new task instead of as part of Privacy Browser.
5560 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5563 startActivity(emailIntent);
5565 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5567 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
5568 // Open the dialer and load the phone number, but wait for the user to place the call.
5569 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
5571 // Add the phone number to the intent.
5572 dialIntent.setData(Uri.parse(url));
5574 // Open the dialer in a new task instead of as part of Privacy Browser.
5575 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5578 startActivity(dialIntent);
5580 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5582 } else { // Load a system chooser to select an app that can handle the URL.
5583 // Open an app that can handle the URL.
5584 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
5586 // Add the URL to the intent.
5587 genericIntent.setData(Uri.parse(url));
5589 // List all apps that can handle the URL instead of just opening the first one.
5590 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
5592 // Open the app in a new task instead of as part of Privacy Browser.
5593 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5595 // Start the app or display a snackbar if no app is available to handle the URL.
5597 startActivity(genericIntent);
5598 } catch (ActivityNotFoundException exception) {
5599 Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
5602 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5607 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
5609 public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
5610 // Check to see if the resource request is for the main URL.
5611 if (url.equals(nestedScrollWebView.getCurrentUrl())) {
5612 // `return null` loads the resource request, which should never be blocked if it is the main URL.
5616 // 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.
5617 while (ultraPrivacy == null) {
5618 // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown.
5619 synchronized (this) {
5621 // Check to see if the blocklists have been populated after 100 ms.
5623 } catch (InterruptedException exception) {
5629 // Sanitize the URL.
5630 url = sanitizeUrl(url);
5632 // Get a handle for the navigation view.
5633 NavigationView navigationView = findViewById(R.id.navigationview);
5635 // Get a handle for the navigation menu.
5636 Menu navigationMenu = navigationView.getMenu();
5638 // Get a handle for the navigation requests menu item. The menu is 0 based.
5639 MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
5641 // Create an empty web resource response to be used if the resource request is blocked.
5642 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
5644 // Reset the whitelist results tracker.
5645 String[] whitelistResultStringArray = null;
5647 // Initialize the third party request tracker.
5648 boolean isThirdPartyRequest = false;
5650 // Get the current URL. `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
5651 String currentBaseDomain = nestedScrollWebView.getCurrentDomainName();
5653 // Store a copy of the current domain for use in later requests.
5654 String currentDomain = currentBaseDomain;
5656 // Nobody is happy when comparing null strings.
5657 if ((currentBaseDomain != null) && (url != null)) {
5658 // Convert the request URL to a URI.
5659 Uri requestUri = Uri.parse(url);
5661 // Get the request host name.
5662 String requestBaseDomain = requestUri.getHost();
5664 // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
5665 if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
5666 // Determine the current base domain.
5667 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
5668 // Remove the first subdomain.
5669 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
5672 // Determine the request base domain.
5673 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
5674 // Remove the first subdomain.
5675 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
5678 // Update the third party request tracker.
5679 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
5683 // Get the current WebView page position.
5684 int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5686 // Determine if the WebView is currently displayed.
5687 boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
5689 // Block third-party requests if enabled.
5690 if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
5691 // Add the result to the resource requests.
5692 nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url});
5694 // Increment the blocked requests counters.
5695 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5696 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS);
5698 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5699 if (webViewDisplayed) {
5700 // Updating the UI must be run from the UI thread.
5701 activity.runOnUiThread(() -> {
5702 // Update the menu item titles.
5703 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5705 // Update the options menu if it has been populated.
5706 if (optionsMenu != null) {
5707 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5708 optionsMenu.findItem(R.id.block_all_third_party_requests).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
5709 getString(R.string.block_all_third_party_requests));
5714 // Return an empty web resource response.
5715 return emptyWebResourceResponse;
5718 // Check UltraList if it is enabled.
5719 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) {
5720 // Check the URL against UltraList.
5721 String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
5723 // Process the UltraList results.
5724 if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraLists's blacklist.
5725 // Add the result to the resource requests.
5726 nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5728 // Increment the blocked requests counters.
5729 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5730 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST);
5732 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5733 if (webViewDisplayed) {
5734 // Updating the UI must be run from the UI thread.
5735 activity.runOnUiThread(() -> {
5736 // Update the menu item titles.
5737 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5739 // Update the options menu if it has been populated.
5740 if (optionsMenu != null) {
5741 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5742 optionsMenu.findItem(R.id.ultralist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
5747 // The resource request was blocked. Return an empty web resource response.
5748 return emptyWebResourceResponse;
5749 } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraList's whitelist.
5750 // Add a whitelist entry to the resource requests array.
5751 nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5753 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
5758 // Check UltraPrivacy if it is enabled.
5759 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) {
5760 // Check the URL against UltraPrivacy.
5761 String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
5763 // Process the UltraPrivacy results.
5764 if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraPrivacy's blacklist.
5765 // Add the result to the resource requests.
5766 nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5767 ultraPrivacyResults[5]});
5769 // Increment the blocked requests counters.
5770 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5771 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY);
5773 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5774 if (webViewDisplayed) {
5775 // Updating the UI must be run from the UI thread.
5776 activity.runOnUiThread(() -> {
5777 // Update the menu item titles.
5778 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5780 // Update the options menu if it has been populated.
5781 if (optionsMenu != null) {
5782 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5783 optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
5788 // The resource request was blocked. Return an empty web resource response.
5789 return emptyWebResourceResponse;
5790 } else if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraPrivacy's whitelist.
5791 // Add a whitelist entry to the resource requests array.
5792 nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5793 ultraPrivacyResults[5]});
5795 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
5800 // Check EasyList if it is enabled.
5801 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) {
5802 // Check the URL against EasyList.
5803 String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
5805 // Process the EasyList results.
5806 if (easyListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyList's blacklist.
5807 // Add the result to the resource requests.
5808 nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]});
5810 // Increment the blocked requests counters.
5811 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5812 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST);
5814 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5815 if (webViewDisplayed) {
5816 // Updating the UI must be run from the UI thread.
5817 activity.runOnUiThread(() -> {
5818 // Update the menu item titles.
5819 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5821 // Update the options menu if it has been populated.
5822 if (optionsMenu != null) {
5823 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5824 optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
5829 // The resource request was blocked. Return an empty web resource response.
5830 return emptyWebResourceResponse;
5831 } else if (easyListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyList's whitelist.
5832 // Update the whitelist result string array tracker.
5833 whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]};
5837 // Check EasyPrivacy if it is enabled.
5838 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) {
5839 // Check the URL against EasyPrivacy.
5840 String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
5842 // Process the EasyPrivacy results.
5843 if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyPrivacy's blacklist.
5844 // Add the result to the resource requests.
5845 nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
5846 easyPrivacyResults[5]});
5848 // Increment the blocked requests counters.
5849 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5850 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY);
5852 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5853 if (webViewDisplayed) {
5854 // Updating the UI must be run from the UI thread.
5855 activity.runOnUiThread(() -> {
5856 // Update the menu item titles.
5857 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5859 // Update the options menu if it has been populated.
5860 if (optionsMenu != null) {
5861 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5862 optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
5867 // The resource request was blocked. Return an empty web resource response.
5868 return emptyWebResourceResponse;
5869 } else if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyPrivacy's whitelist.
5870 // Update the whitelist result string array tracker.
5871 whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]};
5875 // Check Fanboy’s Annoyance List if it is enabled.
5876 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
5877 // Check the URL against Fanboy's Annoyance List.
5878 String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
5880 // Process the Fanboy's Annoyance List results.
5881 if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Annoyance List's blacklist.
5882 // Add the result to the resource requests.
5883 nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5884 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]});
5886 // Increment the blocked requests counters.
5887 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5888 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST);
5890 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5891 if (webViewDisplayed) {
5892 // Updating the UI must be run from the UI thread.
5893 activity.runOnUiThread(() -> {
5894 // Update the menu item titles.
5895 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5897 // Update the options menu if it has been populated.
5898 if (optionsMenu != null) {
5899 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5900 optionsMenu.findItem(R.id.fanboys_annoyance_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
5901 getString(R.string.fanboys_annoyance_list));
5906 // The resource request was blocked. Return an empty web resource response.
5907 return emptyWebResourceResponse;
5908 } else if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)){ // The resource request matched Fanboy's Annoyance List's whitelist.
5909 // Update the whitelist result string array tracker.
5910 whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5911 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
5913 } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
5914 // Check the URL against Fanboy's Annoyance List.
5915 String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
5917 // Process the Fanboy's Social Blocking List results.
5918 if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Social Blocking List's blacklist.
5919 // Add the result to the resource requests.
5920 nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5921 fanboysSocialListResults[4], fanboysSocialListResults[5]});
5923 // Increment the blocked requests counters.
5924 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5925 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST);
5927 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5928 if (webViewDisplayed) {
5929 // Updating the UI must be run from the UI thread.
5930 activity.runOnUiThread(() -> {
5931 // Update the menu item titles.
5932 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5934 // Update the options menu if it has been populated.
5935 if (optionsMenu != null) {
5936 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5937 optionsMenu.findItem(R.id.fanboys_social_blocking_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
5938 getString(R.string.fanboys_social_blocking_list));
5943 // The resource request was blocked. Return an empty web resource response.
5944 return emptyWebResourceResponse;
5945 } else if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched Fanboy's Social Blocking List's whitelist.
5946 // Update the whitelist result string array tracker.
5947 whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5948 fanboysSocialListResults[4], fanboysSocialListResults[5]};
5952 // Add the request to the log because it hasn't been processed by any of the previous checks.
5953 if (whitelistResultStringArray != null) { // The request was processed by a whitelist.
5954 nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
5955 } else { // The request didn't match any blocklist entry. Log it as a default request.
5956 nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_DEFAULT, url});
5959 // The resource request has not been blocked. `return null` loads the requested resource.
5963 // Handle HTTP authentication requests.
5965 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
5966 // Store the handler.
5967 nestedScrollWebView.setHttpAuthHandler(handler);
5969 // Instantiate an HTTP authentication dialog.
5970 DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId());
5972 // Show the HTTP authentication dialog.
5973 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
5977 public void onPageStarted(WebView view, String url, Bitmap favicon) {
5978 // Get the preferences.
5979 boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
5980 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
5982 // Get a handler for the app bar layout.
5983 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
5985 // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.
5987 // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
5988 swipeRefreshLayout.setPadding(0, 0, 0, 0);
5990 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5991 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
5993 // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated.
5994 int appBarHeight = appBarLayout.getHeight();
5996 // The swipe refresh layout must be manually moved below the app bar layout.
5997 swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
5999 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
6000 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
6003 // Reset the list of resource requests.
6004 nestedScrollWebView.clearResourceRequests();
6006 // Reset the requests counters.
6007 nestedScrollWebView.resetRequestsCounters();
6009 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
6010 if (nestedScrollWebView.getNightMode()) {
6011 nestedScrollWebView.setVisibility(View.INVISIBLE);
6013 nestedScrollWebView.setVisibility(View.VISIBLE);
6016 // Hide the keyboard.
6017 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
6019 // Get the current page position.
6020 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6022 // Update the URL text bar if the page is currently selected.
6023 if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
6024 // Clear the focus from the URL edit text.
6025 urlEditText.clearFocus();
6027 // Display the formatted URL text.
6028 urlEditText.setText(url);
6030 // Apply text highlighting to `urlTextBox`.
6034 // Reset the list of host IP addresses.
6035 nestedScrollWebView.clearCurrentIpAddresses();
6037 // Get a URI for the current URL.
6038 Uri currentUri = Uri.parse(url);
6040 // Get the IP addresses for the host.
6041 new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
6043 // Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.)
6044 if (optionsMenu != null) {
6045 // Get a handle for the refresh menu item.
6046 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
6049 refreshMenuItem.setTitle(R.string.stop);
6051 // Get the app bar and theme preferences.
6052 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
6054 // If the icon is displayed in the AppBar, set it according to the theme.
6055 if (displayAdditionalAppBarIcons) {
6057 refreshMenuItem.setIcon(R.drawable.close_dark);
6059 refreshMenuItem.setIcon(R.drawable.close_light);
6066 public void onPageFinished(WebView view, String url) {
6067 // Flush any cookies to persistent storage. The cookie manager has become very lazy about flushing cookies in recent versions.
6068 if (nestedScrollWebView.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) {
6069 CookieManager.getInstance().flush();
6072 // Update the Refresh menu item if the options menu has been created.
6073 if (optionsMenu != null) {
6074 // Get a handle for the refresh menu item.
6075 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
6077 // Reset the Refresh title.
6078 refreshMenuItem.setTitle(R.string.refresh);
6080 // Get the app bar and theme preferences.
6081 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
6082 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
6084 // If the icon is displayed in the AppBar, reset it according to the theme.
6085 if (displayAdditionalAppBarIcons) {
6087 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
6089 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
6094 // Clear the cache and history if Incognito Mode is enabled.
6095 if (incognitoModeEnabled) {
6096 // Clear the cache. `true` includes disk files.
6097 nestedScrollWebView.clearCache(true);
6099 // Clear the back/forward history.
6100 nestedScrollWebView.clearHistory();
6102 // Manually delete cache folders.
6104 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
6105 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
6106 String privateDataDirectoryString = getApplicationInfo().dataDir;
6108 // Delete the main cache directory.
6109 Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
6111 // Delete the secondary `Service Worker` cache directory.
6112 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
6113 Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
6114 } catch (IOException e) {
6115 // Do nothing if an error is thrown.
6119 // Get the current page position.
6120 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6122 // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
6123 if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
6124 !nestedScrollWebView.ignorePinnedDomainInformation()) {
6125 CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
6128 // 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.
6129 String currentUrl = nestedScrollWebView.getUrl();
6131 // Get the current tab.
6132 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
6134 // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
6135 // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
6136 // Probably some sort of race condition when Privacy Browser is being resumed.
6137 if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
6138 // Check to see if the URL is `about:blank`.
6139 if (currentUrl.equals("about:blank")) { // The WebView is blank.
6140 // Display the hint in the URL edit text.
6141 urlEditText.setText("");
6143 // Request focus for the URL text box.
6144 urlEditText.requestFocus();
6146 // Display the keyboard.
6147 inputMethodManager.showSoftInput(urlEditText, 0);
6149 // Apply the domain settings. This clears any settings from the previous domain.
6150 applyDomainSettings(nestedScrollWebView, "", true, false);
6152 // Only populate the title text view if the tab has been fully created.
6154 // Get the custom view from the tab.
6155 View tabView = tab.getCustomView();
6157 // Remove the incorrect warning below that the current tab view might be null.
6158 assert tabView != null;
6160 // Get the title text view from the tab.
6161 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6163 // Set the title as the tab text.
6164 tabTitleTextView.setText(R.string.new_tab);
6166 } else { // The WebView has loaded a webpage.
6167 // Update the URL edit text if it is not currently being edited.
6168 if (!urlEditText.hasFocus()) {
6169 // Sanitize the current URL. This removes unwanted URL elements that were added by redirects, so that they won't be included if the URL is shared.
6170 String sanitizedUrl = sanitizeUrl(currentUrl);
6172 // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
6173 urlEditText.setText(sanitizedUrl);
6175 // Apply text highlighting to the URL.
6179 // Only populate the title text view if the tab has been fully created.
6181 // Get the custom view from the tab.
6182 View tabView = tab.getCustomView();
6184 // Remove the incorrect warning below that the current tab view might be null.
6185 assert tabView != null;
6187 // Get the title text view from the tab.
6188 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6190 // Set the title as the tab text. Sometimes `onReceivedTitle()` is not called, especially when navigating history.
6191 tabTitleTextView.setText(nestedScrollWebView.getTitle());
6197 // Handle SSL Certificate errors.
6199 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
6200 // Get the current website SSL certificate.
6201 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
6203 // Extract the individual pieces of information from the current website SSL certificate.
6204 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
6205 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
6206 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
6207 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
6208 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
6209 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
6210 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
6211 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
6213 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
6214 if (nestedScrollWebView.hasPinnedSslCertificate()) {
6215 // Get the pinned SSL certificate.
6216 ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
6218 // Extract the arrays from the array list.
6219 String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
6220 Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
6222 // Check if the current SSL certificate matches the pinned certificate.
6223 if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
6224 currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) &&
6225 currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) &&
6226 currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
6228 // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
6231 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
6232 // Store the SSL error handler.
6233 nestedScrollWebView.setSslErrorHandler(handler);
6235 // Instantiate an SSL certificate error alert dialog.
6236 DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
6238 // Show the SSL certificate error dialog.
6239 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
6244 // Check to see if this is the first page.
6245 if (pageNumber == 0) {
6246 // Set this nested scroll WebView as the current WebView.
6247 currentWebView = nestedScrollWebView;
6249 // Apply the app settings from the shared preferences.
6252 // Initialize the URL to load string.
6253 String urlToLoadString;
6255 // Get the intent that started the app.
6256 Intent launchingIntent = getIntent();
6258 // Get the information from the intent.
6259 String launchingIntentAction = launchingIntent.getAction();
6260 Uri launchingIntentUriData = launchingIntent.getData();
6262 // Parse the launching intent URL.
6263 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { // The intent contains a search string.
6264 // Create an encoded URL string.
6265 String encodedUrlString;
6267 // Sanitize the search input and convert it to a search.
6269 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
6270 } catch (UnsupportedEncodingException exception) {
6271 encodedUrlString = "";
6274 // Store the web search as the URL to load.
6275 urlToLoadString = searchURL + encodedUrlString;
6276 } else if (launchingIntentUriData != null){ // The intent contains a URL.
6278 urlToLoadString = launchingIntentUriData.toString();
6279 } else { // The is no URL in the intent.
6280 // Store the homepage to be loaded.
6281 urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
6284 // Load the website if not waiting for the proxy.
6285 if (waitingForProxy) { // Store the URL to be loaded in the Nested Scroll WebView.
6286 nestedScrollWebView.setWaitingForProxyUrlString(urlToLoadString);
6287 } else { // Load the URL.
6288 loadUrl(nestedScrollWebView, urlToLoadString);
6290 } else { // This is not the first tab.
6291 // Apply the domain settings.
6292 applyDomainSettings(nestedScrollWebView, url, false, false);
6295 nestedScrollWebView.loadUrl(url, customHeaders);
6297 // Set the focus and display the keyboard if the URL is blank.
6298 if (url.equals("")) {
6299 // Request focus for the URL text box.
6300 urlEditText.requestFocus();
6302 // Create a display keyboard handler.
6303 Handler displayKeyboardHandler = new Handler();
6305 // Create a display keyboard runnable.
6306 Runnable displayKeyboardRunnable = () -> {
6307 // Display the keyboard.
6308 inputMethodManager.showSoftInput(urlEditText, 0);
6311 // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
6312 displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100);