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 a Cancel entry, which by default closes the context menu.
2135 menu.add(R.string.cancel);
2138 case WebView.HitTestResult.EMAIL_TYPE:
2139 // Get the target URL.
2140 linkUrl = hitTestResult.getExtra();
2142 // Set the target URL as the title of the `ContextMenu`.
2143 menu.setHeaderTitle(linkUrl);
2145 // Add a Write Email entry.
2146 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2147 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2148 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2150 // Parse the url and set it as the data for the `Intent`.
2151 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2153 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2154 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2157 startActivity(emailIntent);
2159 // Consume the event.
2163 // Add a Copy Email Address entry.
2164 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2165 // Save the email address in a `ClipData`.
2166 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2168 // Set the `ClipData` as the clipboard's primary clip.
2169 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2171 // Consume the event.
2175 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
2176 menu.add(R.string.cancel);
2179 // `IMAGE_TYPE` is an image.
2180 case WebView.HitTestResult.IMAGE_TYPE:
2181 // Get the image URL.
2182 imageUrl = hitTestResult.getExtra();
2184 // Set the image URL as the title of the context menu.
2185 menu.setHeaderTitle(imageUrl);
2187 // Add an Open in New Tab entry.
2188 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2189 // Load the image in a new tab.
2190 addNewTab(imageUrl, true);
2192 // Consume the event.
2196 // Add a View Image entry.
2197 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2198 // Load the image in the current tab.
2199 loadUrl(currentWebView, imageUrl);
2201 // Consume the event.
2205 // Add a Save Image entry.
2206 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2207 // Instantiate the save dialog.
2208 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
2209 currentWebView.getAcceptFirstPartyCookies());
2211 // Show the save dialog. It must be named `save_dialog` so that the file picked can update the file name.
2212 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
2214 // Consume the event.
2218 // Add a Copy URL entry.
2219 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2220 // Save the image URL in a clip data.
2221 ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2223 // Set the clip data as the clipboard's primary clip.
2224 clipboardManager.setPrimaryClip(imageTypeClipData);
2226 // Consume the event.
2230 // Add an Open with App entry.
2231 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2232 // Open the image URL with an external app.
2233 openWithApp(imageUrl);
2235 // Consume the event.
2239 // Add an Open with Browser entry.
2240 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2241 // Open the image URL with an external browser.
2242 openWithBrowser(imageUrl);
2244 // Consume the event.
2248 // Add a Cancel entry, which by default closes the context menu.
2249 menu.add(R.string.cancel);
2252 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2253 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2254 // Get the image URL.
2255 imageUrl = hitTestResult.getExtra();
2257 // Instantiate a handler.
2258 Handler handler = new Handler();
2260 // Get a message from the handler.
2261 Message message = handler.obtainMessage();
2263 // Request the image details from the last touched node be returned in the message.
2264 currentWebView.requestFocusNodeHref(message);
2266 // Get the link URL from the message data.
2267 linkUrl = message.getData().getString("url");
2269 // Set the link URL as the title of the context menu.
2270 menu.setHeaderTitle(linkUrl);
2272 // Add an Open in New Tab entry.
2273 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2274 // Load the link URL in a new tab and move to it.
2275 addNewTab(linkUrl, true);
2277 // Consume the event.
2281 // Add an Open in Background entry.
2282 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2283 // Lod the link URL in a new tab but do not move to it.
2284 addNewTab(linkUrl, false);
2286 // Consume the event.
2290 // Add an Open Image in New Tab entry.
2291 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2292 // Load the image in a new tab and move to it.
2293 addNewTab(imageUrl, true);
2295 // Consume the event.
2299 // Add a View Image entry.
2300 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2301 // View the image in the current tab.
2302 loadUrl(currentWebView, imageUrl);
2304 // Consume the event.
2308 // Add a Copy URL entry.
2309 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2310 // Save the link URL in a clip data.
2311 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2313 // Set the clip data as the clipboard's primary clip.
2314 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2316 // Consume the event.
2320 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2321 // Instantiate the save dialog.
2322 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, imageUrl, currentWebView.getSettings().getUserAgentString(),
2323 currentWebView.getAcceptFirstPartyCookies());
2325 // Show the save raw dialog. It must be named `save_dialog` so that the file picked can update the file name.
2326 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
2328 // Consume the event.
2332 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2333 // Instantiate the save dialog.
2334 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, linkUrl, currentWebView.getSettings().getUserAgentString(),
2335 currentWebView.getAcceptFirstPartyCookies());
2337 // Show the save raw dialog. It must be named `save_dialog` so that the file picked can update the file name.
2338 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
2340 // Consume the event.
2344 // Add an Open with App entry.
2345 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2346 // Open the link URL with an external app.
2347 openWithApp(linkUrl);
2349 // Consume the event.
2353 // Add an Open with Browser entry.
2354 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2355 // Open the link URL with an external browser.
2356 openWithBrowser(linkUrl);
2358 // Consume the event.
2362 // Add a cancel entry, which by default closes the context menu.
2363 menu.add(R.string.cancel);
2369 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2370 // Get a handle for the bookmarks list view.
2371 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2374 Dialog dialog = dialogFragment.getDialog();
2376 // Remove the incorrect lint warning below that the dialog might be null.
2377 assert dialog != null;
2379 // Get the views from the dialog fragment.
2380 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2381 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2383 // Extract the strings from the edit texts.
2384 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2385 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2387 // Create a favorite icon byte array output stream.
2388 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2390 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2391 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2393 // Convert the favorite icon byte array stream to a byte array.
2394 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2396 // Display the new bookmark below the current items in the (0 indexed) list.
2397 int newBookmarkDisplayOrder = bookmarksListView.getCount();
2399 // Create the bookmark.
2400 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2402 // Update the bookmarks cursor with the current contents of this folder.
2403 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2405 // Update the list view.
2406 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2408 // Scroll to the new bookmark.
2409 bookmarksListView.setSelection(newBookmarkDisplayOrder);
2413 public void onCreateBookmarkFolder(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2414 // Get a handle for the bookmarks list view.
2415 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2418 Dialog dialog = dialogFragment.getDialog();
2420 // Remove the incorrect lint warning below that the dialog might be null.
2421 assert dialog != null;
2423 // Get handles for the views in the dialog fragment.
2424 EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
2425 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
2426 ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
2428 // Get new folder name string.
2429 String folderNameString = createFolderNameEditText.getText().toString();
2431 // Create a folder icon bitmap.
2432 Bitmap folderIconBitmap;
2434 // Set the folder icon bitmap according to the dialog.
2435 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
2436 // Get the default folder icon drawable.
2437 Drawable folderIconDrawable = folderIconImageView.getDrawable();
2439 // Convert the folder icon drawable to a bitmap drawable.
2440 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2442 // Convert the folder icon bitmap drawable to a bitmap.
2443 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2444 } else { // Use the WebView favorite icon.
2445 // Copy the favorite icon bitmap to the folder icon bitmap.
2446 folderIconBitmap = favoriteIconBitmap;
2449 // Create a folder icon byte array output stream.
2450 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2452 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2453 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2455 // Convert the folder icon byte array stream to a byte array.
2456 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2458 // Move all the bookmarks down one in the display order.
2459 for (int i = 0; i < bookmarksListView.getCount(); i++) {
2460 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2461 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2464 // Create the folder, which will be placed at the top of the `ListView`.
2465 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2467 // Update the bookmarks cursor with the current contents of this folder.
2468 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2470 // Update the `ListView`.
2471 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2473 // Scroll to the new folder.
2474 bookmarksListView.setSelection(0);
2478 public void onSaveBookmark(DialogFragment dialogFragment, int selectedBookmarkDatabaseId, Bitmap favoriteIconBitmap) {
2480 Dialog dialog = dialogFragment.getDialog();
2482 // Remove the incorrect lint warning below that the dialog might be null.
2483 assert dialog != null;
2485 // Get handles for the views from the dialog.
2486 EditText editBookmarkNameEditText = dialog.findViewById(R.id.edit_bookmark_name_edittext);
2487 EditText editBookmarkUrlEditText = dialog.findViewById(R.id.edit_bookmark_url_edittext);
2488 RadioButton currentBookmarkIconRadioButton = dialog.findViewById(R.id.edit_bookmark_current_icon_radiobutton);
2490 // Store the bookmark strings.
2491 String bookmarkNameString = editBookmarkNameEditText.getText().toString();
2492 String bookmarkUrlString = editBookmarkUrlEditText.getText().toString();
2494 // Update the bookmark.
2495 if (currentBookmarkIconRadioButton.isChecked()) { // Update the bookmark without changing the favorite icon.
2496 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString);
2497 } else { // Update the bookmark using the `WebView` favorite icon.
2498 // Create a favorite icon byte array output stream.
2499 ByteArrayOutputStream newFavoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2501 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2502 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFavoriteIconByteArrayOutputStream);
2504 // Convert the favorite icon byte array stream to a byte array.
2505 byte[] newFavoriteIconByteArray = newFavoriteIconByteArrayOutputStream.toByteArray();
2507 // Update the bookmark and the favorite icon.
2508 bookmarksDatabaseHelper.updateBookmark(selectedBookmarkDatabaseId, bookmarkNameString, bookmarkUrlString, newFavoriteIconByteArray);
2511 // Update the bookmarks cursor with the current contents of this folder.
2512 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2514 // Update the list view.
2515 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2519 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
2521 Dialog dialog = dialogFragment.getDialog();
2523 // Remove the incorrect lint warning below that the dialog might be null.
2524 assert dialog != null;
2526 // Get handles for the views from `dialogFragment`.
2527 EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
2528 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
2529 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
2530 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
2532 // Get the new folder name.
2533 String newFolderNameString = editFolderNameEditText.getText().toString();
2535 // Check if the favorite icon has changed.
2536 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
2537 // Update the name in the database.
2538 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2539 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
2540 // Create the new folder icon Bitmap.
2541 Bitmap folderIconBitmap;
2543 // Populate the new folder icon bitmap.
2544 if (defaultFolderIconRadioButton.isChecked()) {
2545 // Get the default folder icon drawable.
2546 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2548 // Convert the folder icon drawable to a bitmap drawable.
2549 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2551 // Convert the folder icon bitmap drawable to a bitmap.
2552 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2553 } else { // Use the `WebView` favorite icon.
2554 // Copy the favorite icon bitmap to the folder icon bitmap.
2555 folderIconBitmap = favoriteIconBitmap;
2558 // Create a folder icon byte array output stream.
2559 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2561 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2562 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2564 // Convert the folder icon byte array stream to a byte array.
2565 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2567 // Update the folder icon in the database.
2568 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2569 } else { // The folder icon and the name have changed.
2570 // Get the new folder icon `Bitmap`.
2571 Bitmap folderIconBitmap;
2572 if (defaultFolderIconRadioButton.isChecked()) {
2573 // Get the default folder icon drawable.
2574 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2576 // Convert the folder icon drawable to a bitmap drawable.
2577 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2579 // Convert the folder icon bitmap drawable to a bitmap.
2580 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2581 } else { // Use the `WebView` favorite icon.
2582 // Copy the favorite icon bitmap to the folder icon bitmap.
2583 folderIconBitmap = favoriteIconBitmap;
2586 // Create a folder icon byte array output stream.
2587 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2589 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2590 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2592 // Convert the folder icon byte array stream to a byte array.
2593 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2595 // Update the folder name and icon in the database.
2596 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2599 // Update the bookmarks cursor with the current contents of this folder.
2600 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2602 // Update the `ListView`.
2603 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2606 // Override `onBackPressed` to handle the navigation drawer and and the WebViews.
2608 public void onBackPressed() {
2609 // Get a handle for the drawer layout and the tab layout.
2610 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2611 TabLayout tabLayout = findViewById(R.id.tablayout);
2613 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
2614 // Close the navigation drawer.
2615 drawerLayout.closeDrawer(GravityCompat.START);
2616 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
2617 if (currentBookmarksFolder.isEmpty()) { // The home folder is displayed.
2618 // close the bookmarks drawer.
2619 drawerLayout.closeDrawer(GravityCompat.END);
2620 } else { // A subfolder is displayed.
2621 // Place the former parent folder in `currentFolder`.
2622 currentBookmarksFolder = bookmarksDatabaseHelper.getParentFolderName(currentBookmarksFolder);
2624 // Load the new folder.
2625 loadBookmarksFolder();
2627 } else if (displayingFullScreenVideo) { // A full screen video is shown.
2628 // Get a handle for the layouts.
2629 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
2630 RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
2631 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
2633 // Re-enable the screen timeout.
2634 fullScreenVideoFrameLayout.setKeepScreenOn(false);
2636 // Unset the full screen video flag.
2637 displayingFullScreenVideo = false;
2639 // Remove all the views from the full screen video frame layout.
2640 fullScreenVideoFrameLayout.removeAllViews();
2642 // Hide the full screen video frame layout.
2643 fullScreenVideoFrameLayout.setVisibility(View.GONE);
2645 // Enable the sliding drawers.
2646 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
2648 // Show the main content relative layout.
2649 mainContentRelativeLayout.setVisibility(View.VISIBLE);
2651 // Apply the appropriate full screen mode flags.
2652 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
2653 // Hide the app bar if specified.
2655 // Get handles for the views.
2656 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
2657 ActionBar actionBar = getSupportActionBar();
2659 // Remove the incorrect lint warning below that the action bar might be null.
2660 assert actionBar != null;
2662 // Hide the tab linear layout.
2663 tabsLinearLayout.setVisibility(View.GONE);
2665 // Hide the action bar.
2669 // Hide the banner ad in the free flavor.
2670 if (BuildConfig.FLAVOR.contentEquals("free")) {
2671 AdHelper.hideAd(findViewById(R.id.adview));
2674 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
2675 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2677 /* Hide the system bars.
2678 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
2679 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
2680 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
2681 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
2683 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
2684 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
2685 } else { // Switch to normal viewing mode.
2686 // Remove the `SYSTEM_UI` flags from the root frame layout.
2687 rootFrameLayout.setSystemUiVisibility(0);
2689 // Add the translucent status flag.
2690 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
2693 // Reload the ad for the free flavor if not in full screen mode.
2694 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2696 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2698 } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history.
2699 // Get the current web back forward list.
2700 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2702 // Get the previous entry URL.
2703 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2705 // Apply the domain settings.
2706 applyDomainSettings(currentWebView, previousUrl, false, false);
2709 currentWebView.goBack();
2710 } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs.
2711 // Close the current tab.
2713 } else { // There isn't anything to do in Privacy Browser.
2714 // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
2715 if (Build.VERSION.SDK_INT >= 21) {
2716 finishAndRemoveTask();
2721 // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted.
2726 // Process the results of a file browse.
2728 public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
2729 // Run the default commands.
2730 super.onActivityResult(requestCode, resultCode, returnedIntent);
2732 // Run the commands that correlate to the specified request code.
2733 switch (requestCode) {
2734 case BROWSE_FILE_UPLOAD_REQUEST_CODE:
2735 // File uploads only work on API >= 21.
2736 if (Build.VERSION.SDK_INT >= 21) {
2737 // Pass the file to the WebView.
2738 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
2742 case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
2743 // Don't do anything if the user pressed back from the file picker.
2744 if (resultCode == Activity.RESULT_OK) {
2745 // Get a handle for the save dialog fragment.
2746 DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
2748 // Only update the file name if the dialog still exists.
2749 if (saveWebpageDialogFragment != null) {
2750 // Get a handle for the save webpage dialog.
2751 Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
2753 // Remove the incorrect lint warning below that the dialog might be null.
2754 assert saveWebpageDialog != null;
2756 // Get a handle for the file name edit text.
2757 EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
2758 TextView fileExistsWarningTextView = saveWebpageDialog.findViewById(R.id.file_exists_warning_textview);
2760 // Instantiate the file name helper.
2761 FileNameHelper fileNameHelper = new FileNameHelper();
2763 // Get the file path if it isn't null.
2764 if (returnedIntent.getData() != null) {
2765 // Convert the file name URI to a file name path.
2766 String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
2768 // Set the file name path as the text of the file name edit text.
2769 fileNameEditText.setText(fileNamePath);
2771 // Move the cursor to the end of the file name edit text.
2772 fileNameEditText.setSelection(fileNamePath.length());
2774 // Hide the file exists warning.
2775 fileExistsWarningTextView.setVisibility(View.GONE);
2781 case BROWSE_OPEN_REQUEST_CODE:
2782 // Don't do anything if the user pressed back from the file picker.
2783 if (resultCode == Activity.RESULT_OK) {
2784 // Get a handle for the open dialog fragment.
2785 DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
2787 // Only update the file name if the dialog still exists.
2788 if (openDialogFragment != null) {
2789 // Get a handle for the open dialog.
2790 Dialog openDialog = openDialogFragment.getDialog();
2792 // Remove the incorrect lint warning below that the dialog might be null.
2793 assert openDialog != null;
2795 // Get a handle for the file name edit text.
2796 EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
2798 // Instantiate the file name helper.
2799 FileNameHelper fileNameHelper = new FileNameHelper();
2801 // Get the file path if it isn't null.
2802 if (returnedIntent.getData() != null) {
2803 // Convert the file name URI to a file name path.
2804 String fileNamePath = fileNameHelper.convertUriToFileNamePath(returnedIntent.getData());
2806 // Set the file name path as the text of the file name edit text.
2807 fileNameEditText.setText(fileNamePath);
2809 // Move the cursor to the end of the file name edit text.
2810 fileNameEditText.setSelection(fileNamePath.length());
2818 private void loadUrlFromTextBox() {
2819 // Get a handle for the URL edit text.
2820 EditText urlEditText = findViewById(R.id.url_edittext);
2822 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
2823 String unformattedUrlString = urlEditText.getText().toString().trim();
2825 // Initialize the formatted URL string.
2828 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
2829 if (unformattedUrlString.startsWith("content://")) { // This is a Content URL.
2830 // Load the entire content URL.
2831 url = unformattedUrlString;
2832 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2833 unformattedUrlString.startsWith("file://")) { // This is a standard URL.
2834 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
2835 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2836 unformattedUrlString = "https://" + unformattedUrlString;
2839 // Initialize `unformattedUrl`.
2840 URL unformattedUrl = null;
2842 // 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.
2844 unformattedUrl = new URL(unformattedUrlString);
2845 } catch (MalformedURLException e) {
2846 e.printStackTrace();
2849 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2850 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2851 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2852 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2853 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2854 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2857 Uri.Builder uri = new Uri.Builder();
2858 uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2860 // Decode the URI as a UTF-8 string in.
2862 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2863 } catch (UnsupportedEncodingException exception) {
2864 // Do nothing. The formatted URL string will remain blank.
2866 } else if (!unformattedUrlString.isEmpty()){ // This is not a URL, but rather a search string.
2867 // Create an encoded URL String.
2868 String encodedUrlString;
2870 // Sanitize the search input.
2872 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2873 } catch (UnsupportedEncodingException exception) {
2874 encodedUrlString = "";
2877 // Add the base search URL.
2878 url = searchURL + encodedUrlString;
2881 // 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.
2882 urlEditText.clearFocus();
2885 loadUrl(currentWebView, url);
2888 private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) {
2889 // Sanitize the URL.
2890 url = sanitizeUrl(url);
2892 // Apply the domain settings.
2893 applyDomainSettings(nestedScrollWebView, url, true, false);
2896 nestedScrollWebView.loadUrl(url, customHeaders);
2899 public void findPreviousOnPage(View view) {
2900 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
2901 currentWebView.findNext(false);
2904 public void findNextOnPage(View view) {
2905 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2906 currentWebView.findNext(true);
2909 public void closeFindOnPage(View view) {
2910 // Get a handle for the views.
2911 Toolbar toolbar = findViewById(R.id.toolbar);
2912 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2913 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2915 // Delete the contents of `find_on_page_edittext`.
2916 findOnPageEditText.setText(null);
2918 // Clear the highlighted phrases if the WebView is not null.
2919 if (currentWebView != null) {
2920 currentWebView.clearMatches();
2923 // Hide the find on page linear layout.
2924 findOnPageLinearLayout.setVisibility(View.GONE);
2926 // Show the toolbar.
2927 toolbar.setVisibility(View.VISIBLE);
2929 // Get a handle for the input method manager.
2930 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2932 // Remove the lint warning below that the input method manager might be null.
2933 assert inputMethodManager != null;
2935 // Hide the keyboard.
2936 inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2940 public void onApplyNewFontSize(DialogFragment dialogFragment) {
2942 Dialog dialog = dialogFragment.getDialog();
2944 // Remove the incorrect lint warning below tha the dialog might be null.
2945 assert dialog != null;
2947 // Get a handle for the font size edit text.
2948 EditText fontSizeEditText = dialog.findViewById(R.id.font_size_edittext);
2950 // Initialize the new font size variable with the current font size.
2951 int newFontSize = currentWebView.getSettings().getTextZoom();
2953 // Get the font size from the edit text.
2955 newFontSize = Integer.parseInt(fontSizeEditText.getText().toString());
2956 } catch (Exception exception) {
2957 // If the edit text does not contain a valid font size do nothing.
2960 // Apply the new font size.
2961 currentWebView.getSettings().setTextZoom(newFontSize);
2965 public void onOpen(DialogFragment dialogFragment) {
2967 Dialog dialog = dialogFragment.getDialog();
2969 // Remove the incorrect lint warning below that the dialog might be null.
2970 assert dialog != null;
2972 // Get a handle for the file name edit text.
2973 EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
2975 // Get the file path string.
2976 openFilePath = fileNameEditText.getText().toString();
2978 // Check to see if the storage permission is needed.
2979 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
2981 currentWebView.loadUrl("file://" + openFilePath);
2982 } else { // The storage permission has not been granted.
2983 // Get the external private directory file.
2984 File externalPrivateDirectoryFile = getExternalFilesDir(null);
2986 // Remove the incorrect lint error below that the file might be null.
2987 assert externalPrivateDirectoryFile != null;
2989 // Get the external private directory string.
2990 String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
2992 // Check to see if the file path is in the external private directory.
2993 if (openFilePath.startsWith(externalPrivateDirectory)) { // the file path is in the external private directory.
2995 currentWebView.loadUrl("file://" + openFilePath);
2996 } else { // The file path is in a public directory.
2997 // Check if the user has previously denied the storage permission.
2998 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
2999 // Instantiate the storage permission alert dialog.
3000 DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(StoragePermissionDialog.OPEN);
3002 // Show the storage permission alert dialog. The permission will be requested the the dialog is closed.
3003 storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
3004 } else { // Show the permission request directly.
3005 // Request the write external storage permission. The file will be opened when it finishes.
3006 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE);
3013 public void onSaveWebpage(int saveType, DialogFragment dialogFragment) {
3015 Dialog dialog = dialogFragment.getDialog();
3017 // Remove the incorrect lint warning below that the dialog might be null.
3018 assert dialog != null;
3020 // Get a handle for the edit texts.
3021 EditText urlEditText = dialog.findViewById(R.id.url_edittext);
3022 EditText fileNameEditText = dialog.findViewById(R.id.file_name_edittext);
3024 // Get the strings from the edit texts.
3025 saveWebpageUrl = urlEditText.getText().toString();
3026 saveWebpageFilePath = fileNameEditText.getText().toString();
3028 // Check to see if the storage permission is needed.
3029 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { // The storage permission has been granted.
3030 //Save the webpage according to the save type.
3032 case StoragePermissionDialog.SAVE_URL:
3034 new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
3037 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3038 // Save the webpage archive.
3039 currentWebView.saveWebArchive(saveWebpageFilePath);
3042 case StoragePermissionDialog.SAVE_AS_IMAGE:
3043 // Save the webpage image.
3044 new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
3047 } else { // The storage permission has not been granted.
3048 // Get the external private directory file.
3049 File externalPrivateDirectoryFile = getExternalFilesDir(null);
3051 // Remove the incorrect lint error below that the file might be null.
3052 assert externalPrivateDirectoryFile != null;
3054 // Get the external private directory string.
3055 String externalPrivateDirectory = externalPrivateDirectoryFile.toString();
3057 // Check to see if the file path is in the external private directory.
3058 if (saveWebpageFilePath.startsWith(externalPrivateDirectory)) { // The file path is in the external private directory.
3059 // Save the webpage according to the save type.
3061 case StoragePermissionDialog.SAVE_URL:
3063 new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
3066 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3067 // Save the webpage archive.
3068 currentWebView.saveWebArchive(saveWebpageFilePath);
3071 case StoragePermissionDialog.SAVE_AS_IMAGE:
3072 // Save the webpage image.
3073 new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
3076 } else { // The file path is in a public directory.
3077 // Check if the user has previously denied the storage permission.
3078 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
3079 // Instantiate the storage permission alert dialog.
3080 DialogFragment storagePermissionDialogFragment = StoragePermissionDialog.displayDialog(saveType);
3082 // Show the storage permission alert dialog. The permission will be requested when the dialog is closed.
3083 storagePermissionDialogFragment.show(getSupportFragmentManager(), getString(R.string.storage_permission));
3084 } else { // Show the permission request directly.
3086 case StoragePermissionDialog.SAVE_URL:
3087 // Request the write external storage permission. The URL will be saved when it finishes.
3088 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE);
3090 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3091 // Request the write external storage permission. The webpage archive will be saved when it finishes.
3092 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE);
3095 case StoragePermissionDialog.SAVE_AS_IMAGE:
3096 // Request the write external storage permission. The webpage image will be saved when it finishes.
3097 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE);
3106 public void onCloseStoragePermissionDialog(int requestType) {
3107 switch (requestType) {
3108 case StoragePermissionDialog.OPEN:
3109 // Request the write external storage permission. The file will be opened when it finishes.
3110 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_OPEN_REQUEST_CODE);
3113 case StoragePermissionDialog.SAVE_URL:
3114 // Request the write external storage permission. The URL will be saved when it finishes.
3115 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_URL_REQUEST_CODE);
3118 case StoragePermissionDialog.SAVE_AS_ARCHIVE:
3119 // Request the write external storage permission. The webpage archive will be saved when it finishes.
3120 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE);
3123 case StoragePermissionDialog.SAVE_AS_IMAGE:
3124 // Request the write external storage permission. The webpage image will be saved when it finishes.
3125 ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE);
3131 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
3132 //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).
3133 if (grantResults.length > 0) {
3134 switch (requestCode) {
3135 case PERMISSION_OPEN_REQUEST_CODE:
3136 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3137 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3139 currentWebView.loadUrl("file://" + openFilePath);
3140 } else { // The storage permission was not granted.
3141 // Display an error snackbar.
3142 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3145 // Reset the open file path.
3149 case PERMISSION_SAVE_URL_REQUEST_CODE:
3150 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3151 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3152 // Save the raw URL.
3153 new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl);
3154 } else { // The storage permission was not granted.
3155 // Display an error snackbar.
3156 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3159 // Reset the save strings.
3160 saveWebpageUrl = "";
3161 saveWebpageFilePath = "";
3164 case PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE:
3165 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3166 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3167 // Save the webpage archive.
3168 currentWebView.saveWebArchive(saveWebpageFilePath);
3169 } else { // The storage permission was not granted.
3170 // Display an error snackbar.
3171 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3174 // Reset the save webpage file path.
3175 saveWebpageFilePath = "";
3178 case PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE:
3179 // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty.
3180 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // The storage permission was granted.
3181 // Save the webpage image.
3182 new SaveWebpageImage(this, currentWebView).execute(saveWebpageFilePath);
3183 } else { // The storage permission was not granted.
3184 // Display an error snackbar.
3185 Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show();
3188 // Reset the save webpage file path.
3189 saveWebpageFilePath = "";
3195 private void applyAppSettings() {
3196 // 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.
3197 if (webViewDefaultUserAgent == null) {
3201 // Get a handle for the shared preferences.
3202 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3204 // Store the values from the shared preferences in variables.
3205 incognitoModeEnabled = sharedPreferences.getBoolean("incognito_mode", false);
3206 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", false);
3207 sanitizeGoogleAnalytics = sharedPreferences.getBoolean("google_analytics", true);
3208 sanitizeFacebookClickIds = sharedPreferences.getBoolean("facebook_click_ids", true);
3209 sanitizeTwitterAmpRedirects = sharedPreferences.getBoolean("twitter_amp_redirects", true);
3210 proxyMode = sharedPreferences.getString("proxy", getString(R.string.proxy_default_value));
3211 fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("full_screen_browsing_mode", false);
3212 hideAppBar = sharedPreferences.getBoolean("hide_app_bar", true);
3213 scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
3215 // Get the search string.
3216 String searchString = sharedPreferences.getString("search", getString(R.string.search_default_value));
3218 // Set the search string.
3219 if (searchString.equals("Custom URL")) { // A custom search string is used.
3220 searchURL = sharedPreferences.getString("search_custom_url", getString(R.string.search_custom_url_default_value));
3221 } else { // A custom search string is not used.
3222 searchURL = searchString;
3225 // Get handles for the views that need to be modified.
3226 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
3227 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
3228 ActionBar actionBar = getSupportActionBar();
3229 Toolbar toolbar = findViewById(R.id.toolbar);
3230 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
3231 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
3232 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
3234 // Remove the incorrect lint warning below that the action bar might be null.
3235 assert actionBar != null;
3240 // Set Do Not Track status.
3241 if (doNotTrackEnabled) {
3242 customHeaders.put("DNT", "1");
3244 customHeaders.remove("DNT");
3247 // Get the current layout parameters. Using coordinator layout parameters allows the `setBehavior()` command and using app bar layout parameters allows the `setScrollFlags()` command.
3248 CoordinatorLayout.LayoutParams swipeRefreshLayoutParams = (CoordinatorLayout.LayoutParams) swipeRefreshLayout.getLayoutParams();
3249 AppBarLayout.LayoutParams toolbarLayoutParams = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
3250 AppBarLayout.LayoutParams findOnPageLayoutParams = (AppBarLayout.LayoutParams) findOnPageLinearLayout.getLayoutParams();
3251 AppBarLayout.LayoutParams tabsLayoutParams = (AppBarLayout.LayoutParams) tabsLinearLayout.getLayoutParams();
3253 // Add the scrolling behavior to the layout parameters.
3255 // Enable scrolling of the app bar.
3256 swipeRefreshLayoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
3257 toolbarLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3258 findOnPageLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3259 tabsLayoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
3261 // Disable scrolling of the app bar.
3262 swipeRefreshLayoutParams.setBehavior(null);
3263 toolbarLayoutParams.setScrollFlags(0);
3264 findOnPageLayoutParams.setScrollFlags(0);
3265 tabsLayoutParams.setScrollFlags(0);
3267 // Expand the app bar if it is currently collapsed.
3268 appBarLayout.setExpanded(true);
3271 // Apply the modified layout parameters.
3272 swipeRefreshLayout.setLayoutParams(swipeRefreshLayoutParams);
3273 toolbar.setLayoutParams(toolbarLayoutParams);
3274 findOnPageLinearLayout.setLayoutParams(findOnPageLayoutParams);
3275 tabsLinearLayout.setLayoutParams(tabsLayoutParams);
3277 // Set the app bar scrolling for each WebView.
3278 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3279 // Get the WebView tab fragment.
3280 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3282 // Get the fragment view.
3283 View fragmentView = webViewTabFragment.getView();
3285 // Only modify the WebViews if they exist.
3286 if (fragmentView != null) {
3287 // Get the nested scroll WebView from the tab fragment.
3288 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3290 // Set the app bar scrolling.
3291 nestedScrollWebView.setNestedScrollingEnabled(scrollAppBar);
3295 // Update the full screen browsing mode settings.
3296 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
3297 // Update the visibility of the app bar, which might have changed in the settings.
3299 // Hide the tab linear layout.
3300 tabsLinearLayout.setVisibility(View.GONE);
3302 // Hide the action bar.
3305 // Show the tab linear layout.
3306 tabsLinearLayout.setVisibility(View.VISIBLE);
3308 // Show the action bar.
3312 // Hide the banner ad in the free flavor.
3313 if (BuildConfig.FLAVOR.contentEquals("free")) {
3314 AdHelper.hideAd(findViewById(R.id.adview));
3317 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
3318 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
3320 /* Hide the system bars.
3321 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
3322 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
3323 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
3324 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
3326 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
3327 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
3328 } else { // Privacy Browser is not in full screen browsing mode.
3329 // 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.
3330 inFullScreenBrowsingMode = false;
3332 // Show the tab linear layout.
3333 tabsLinearLayout.setVisibility(View.VISIBLE);
3335 // Show the action bar.
3338 // Show the banner ad in the free flavor.
3339 if (BuildConfig.FLAVOR.contentEquals("free")) {
3340 // Initialize the ads. If this isn't the first run, `loadAd()` will be automatically called instead.
3341 AdHelper.initializeAds(findViewById(R.id.adview), getApplicationContext(), getSupportFragmentManager(), getString(R.string.google_app_id), getString(R.string.ad_unit_id));
3344 // Remove the `SYSTEM_UI` flags from the root frame layout.
3345 rootFrameLayout.setSystemUiVisibility(0);
3347 // Add the translucent status flag.
3348 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
3352 private void initializeApp() {
3353 // Get a handle for the shared preferences.
3354 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3356 // Get the theme preference.
3357 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
3359 // Get a handle for the input method.
3360 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
3362 // Remove the lint warning below that the input method manager might be null.
3363 assert inputMethodManager != null;
3365 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
3366 redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
3367 initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
3368 finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
3370 // Get handles for the URL views.
3371 EditText urlEditText = findViewById(R.id.url_edittext);
3373 // Remove the formatting from the URL edit text when the user is editing the text.
3374 urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
3375 if (hasFocus) { // The user is editing the URL text box.
3376 // Remove the highlighting.
3377 urlEditText.getText().removeSpan(redColorSpan);
3378 urlEditText.getText().removeSpan(initialGrayColorSpan);
3379 urlEditText.getText().removeSpan(finalGrayColorSpan);
3380 } else { // The user has stopped editing the URL text box.
3381 // Move to the beginning of the string.
3382 urlEditText.setSelection(0);
3384 // Reapply the highlighting.
3389 // Set the go button on the keyboard to load the URL in `urlTextBox`.
3390 urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
3391 // If the event is a key-down event on the `enter` button, load the URL.
3392 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
3393 // Load the URL into the mainWebView and consume the event.
3394 loadUrlFromTextBox();
3396 // If the enter key was pressed, consume the event.
3399 // If any other key was pressed, do not consume the event.
3404 // Create an Orbot status broadcast receiver.
3405 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
3407 public void onReceive(Context context, Intent intent) {
3408 // Store the content of the status message in `orbotStatus`.
3409 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
3411 // If Privacy Browser is waiting on the proxy, load the website now that Orbot is connected.
3412 if ((orbotStatus != null) && orbotStatus.equals("ON") && waitingForProxy) {
3413 // Reset the waiting for proxy status.
3414 waitingForProxy = false;
3416 // Get a handle for the waiting for proxy dialog.
3417 DialogFragment waitingForProxyDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog));
3419 // Dismiss the waiting for proxy dialog if it is displayed.
3420 if (waitingForProxyDialogFragment != null) {
3421 waitingForProxyDialogFragment.dismiss();
3424 // Reload existing URLs and load any URLs that are waiting for the proxy.
3425 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
3426 // Get the WebView tab fragment.
3427 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
3429 // Get the fragment view.
3430 View fragmentView = webViewTabFragment.getView();
3432 // Only process the WebViews if they exist.
3433 if (fragmentView != null) {
3434 // Get the nested scroll WebView from the tab fragment.
3435 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
3437 // Get the waiting for proxy URL string.
3438 String waitingForProxyUrlString = nestedScrollWebView.getWaitingForProxyUrlString();
3440 // Load the pending URL if it exists.
3441 if (!waitingForProxyUrlString.isEmpty()) { // A URL is waiting to be loaded.
3443 loadUrl(nestedScrollWebView, waitingForProxyUrlString);
3445 // Reset the waiting for proxy URL string.
3446 nestedScrollWebView.resetWaitingForProxyUrlString();
3447 } else { // No URL is waiting to be loaded.
3448 // Reload the existing URL.
3449 nestedScrollWebView.reload();
3457 // Register the Orbot status broadcast receiver on `this` context.
3458 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
3460 // Get handles for views that need to be modified.
3461 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
3462 NavigationView navigationView = findViewById(R.id.navigationview);
3463 TabLayout tabLayout = findViewById(R.id.tablayout);
3464 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
3465 ViewPager webViewPager = findViewById(R.id.webviewpager);
3466 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
3467 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
3468 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
3469 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
3470 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
3472 // Listen for touches on the navigation menu.
3473 navigationView.setNavigationItemSelectedListener(this);
3475 // Get handles for the navigation menu and the back and forward menu items. The menu is 0 based.
3476 Menu navigationMenu = navigationView.getMenu();
3477 MenuItem navigationBackMenuItem = navigationMenu.getItem(2);
3478 MenuItem navigationForwardMenuItem = navigationMenu.getItem(3);
3479 MenuItem navigationHistoryMenuItem = navigationMenu.getItem(4);
3480 MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
3482 // Update the web view pager every time a tab is modified.
3483 webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
3485 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
3490 public void onPageSelected(int position) {
3491 // Close the find on page bar if it is open.
3492 closeFindOnPage(null);
3494 // Set the current WebView.
3495 setCurrentWebView(position);
3497 // 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.
3498 if (tabLayout.getSelectedTabPosition() != position) {
3499 // Create a handler to select the tab.
3500 Handler selectTabHandler = new Handler();
3502 // Create a runnable to select the tab.
3503 Runnable selectTabRunnable = () -> {
3504 // Get a handle for the tab.
3505 TabLayout.Tab tab = tabLayout.getTabAt(position);
3507 // Assert that the tab is not null.
3514 // Select the tab layout after 150 milliseconds, which leaves enough time for a new tab to be inflated.
3515 selectTabHandler.postDelayed(selectTabRunnable, 150);
3520 public void onPageScrollStateChanged(int state) {
3525 // Display the View SSL Certificate dialog when the currently selected tab is reselected.
3526 tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
3528 public void onTabSelected(TabLayout.Tab tab) {
3529 // Select the same page in the view pager.
3530 webViewPager.setCurrentItem(tab.getPosition());
3534 public void onTabUnselected(TabLayout.Tab tab) {
3539 public void onTabReselected(TabLayout.Tab tab) {
3540 // Instantiate the View SSL Certificate dialog.
3541 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
3543 // Display the View SSL Certificate dialog.
3544 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
3548 // 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.
3549 // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
3551 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
3552 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
3553 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
3554 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
3556 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
3557 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
3558 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
3559 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
3562 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
3563 launchBookmarksActivityFab.setOnClickListener(v -> {
3564 // Get a copy of the favorite icon bitmap.
3565 Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
3567 // Create a favorite icon byte array output stream.
3568 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
3570 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
3571 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
3573 // Convert the favorite icon byte array stream to a byte array.
3574 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
3576 // Create an intent to launch the bookmarks activity.
3577 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
3579 // Add the extra information to the intent.
3580 bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
3581 bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
3582 bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
3583 bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
3586 startActivity(bookmarksIntent);
3589 // Set the create new bookmark folder FAB to display an alert dialog.
3590 createBookmarkFolderFab.setOnClickListener(v -> {
3591 // Create a create bookmark folder dialog.
3592 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
3594 // Show the create bookmark folder dialog.
3595 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
3598 // Set the create new bookmark FAB to display an alert dialog.
3599 createBookmarkFab.setOnClickListener(view -> {
3600 // Instantiate the create bookmark dialog.
3601 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
3603 // Display the create bookmark dialog.
3604 createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
3607 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
3608 findOnPageEditText.addTextChangedListener(new TextWatcher() {
3610 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
3615 public void onTextChanged(CharSequence s, int start, int before, int count) {
3620 public void afterTextChanged(Editable s) {
3621 // 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.
3622 if (currentWebView != null) {
3623 currentWebView.findAllAsync(findOnPageEditText.getText().toString());
3628 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
3629 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
3630 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
3631 // Hide the soft keyboard.
3632 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3634 // Consume the event.
3636 } else { // A different key was pressed.
3637 // Do not consume the event.
3642 // Implement swipe to refresh.
3643 swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
3645 // Store the default progress view offsets for use later in `initializeWebView()`.
3646 defaultProgressViewStartOffset = swipeRefreshLayout.getProgressViewStartOffset();
3647 defaultProgressViewEndOffset = swipeRefreshLayout.getProgressViewEndOffset();
3649 // Set the swipe to refresh color according to the theme.
3651 swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
3652 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
3654 swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
3657 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
3658 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
3659 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
3661 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
3662 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
3664 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
3665 currentBookmarksFolder = "";
3667 // Load the home folder, which is `""` in the database.
3668 loadBookmarksFolder();
3670 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
3671 // Convert the id from long to int to match the format of the bookmarks database.
3672 int databaseId = (int) id;
3674 // Get the bookmark cursor for this ID.
3675 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3677 // Move the bookmark cursor to the first row.
3678 bookmarkCursor.moveToFirst();
3680 // Act upon the bookmark according to the type.
3681 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
3682 // Store the new folder name in `currentBookmarksFolder`.
3683 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3685 // Load the new folder.
3686 loadBookmarksFolder();
3687 } else { // The selected bookmark is not a folder.
3688 // Load the bookmark URL.
3689 loadUrl(currentWebView, bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
3691 // Close the bookmarks drawer.
3692 drawerLayout.closeDrawer(GravityCompat.END);
3695 // Close the `Cursor`.
3696 bookmarkCursor.close();
3699 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
3700 // Convert the database ID from `long` to `int`.
3701 int databaseId = (int) id;
3703 // Find out if the selected bookmark is a folder.
3704 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
3707 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
3708 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
3710 // Instantiate the edit folder bookmark dialog.
3711 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
3713 // Show the edit folder bookmark dialog.
3714 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
3716 // Get the bookmark cursor for this ID.
3717 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseId);
3719 // Move the bookmark cursor to the first row.
3720 bookmarkCursor.moveToFirst();
3722 // Load the bookmark in a new tab but do not switch to the tab or close the drawer.
3723 addNewTab(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)), false);
3726 // Consume the event.
3730 // Get the status bar pixel size.
3731 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
3732 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
3734 // Get the resource density.
3735 float screenDensity = getResources().getDisplayMetrics().density;
3737 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
3738 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
3739 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
3740 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
3742 // The drawer listener is used to update the navigation menu.
3743 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
3745 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
3749 public void onDrawerOpened(@NonNull View drawerView) {
3753 public void onDrawerClosed(@NonNull View drawerView) {
3757 public void onDrawerStateChanged(int newState) {
3758 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
3759 // Get handles for the drawer headers.
3760 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
3761 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
3763 // 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.
3764 if (navigationHeaderTextView != null) {
3765 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
3768 // 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.
3769 if (bookmarksHeaderTextView != null) {
3770 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
3773 // Update the navigation menu items if the WebView is not null.
3774 if (currentWebView != null) {
3775 navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
3776 navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
3777 navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
3778 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
3780 // Hide the keyboard (if displayed).
3781 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
3784 // 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.
3785 urlEditText.clearFocus();
3786 currentWebView.clearFocus();
3791 // 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).
3792 customHeaders.put("X-Requested-With", "");
3794 // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen.
3795 @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
3797 // Get a handle for the WebView.
3798 WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
3800 // Store the default user agent.
3801 webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
3803 // Destroy the bare WebView.
3804 bareWebView.destroy();
3808 public void navigateHistory(String url, int steps) {
3809 // Apply the domain settings.
3810 applyDomainSettings(currentWebView, url, false, false);
3812 // Load the history entry.
3813 currentWebView.goBackOrForward(steps);
3817 public void pinnedErrorGoBack() {
3818 // Get the current web back forward list.
3819 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
3821 // Get the previous entry URL.
3822 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
3824 // Apply the domain settings.
3825 applyDomainSettings(currentWebView, previousUrl, false, false);
3828 currentWebView.goBack();
3831 // `reloadWebsite` is used if returning from the Domains activity. Otherwise JavaScript might not function correctly if it is newly enabled.
3832 @SuppressLint("SetJavaScriptEnabled")
3833 private boolean applyDomainSettings(NestedScrollWebView nestedScrollWebView, String url, boolean resetTab, boolean reloadWebsite) {
3834 // Store a copy of the current user agent to track changes for the return boolean.
3835 String initialUserAgent = nestedScrollWebView.getSettings().getUserAgentString();
3837 // Store the current URL.
3838 nestedScrollWebView.setCurrentUrl(url);
3840 // Parse the URL into a URI.
3841 Uri uri = Uri.parse(url);
3843 // Extract the domain from `uri`.
3844 String newHostName = uri.getHost();
3846 // Strings don't like to be null.
3847 if (newHostName == null) {
3851 // 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.
3852 if (!nestedScrollWebView.getCurrentDomainName().equals(newHostName) || newHostName.equals("")) {
3853 // Set the new host name as the current domain name.
3854 nestedScrollWebView.setCurrentDomainName(newHostName);
3856 // Reset the ignoring of pinned domain information.
3857 nestedScrollWebView.setIgnorePinnedDomainInformation(false);
3859 // Clear any pinned SSL certificate or IP addresses.
3860 nestedScrollWebView.clearPinnedSslCertificate();
3861 nestedScrollWebView.clearPinnedIpAddresses();
3863 // Reset the favorite icon if specified.
3865 // Initialize the favorite icon.
3866 nestedScrollWebView.initializeFavoriteIcon();
3868 // Get the current page position.
3869 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
3871 // Get a handle for the tab layout.
3872 TabLayout tabLayout = findViewById(R.id.tablayout);
3874 // Get the corresponding tab.
3875 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
3877 // Update the tab if it isn't null, which sometimes happens when restarting from the background.
3879 // Get the tab custom view.
3880 View tabCustomView = tab.getCustomView();
3882 // Remove the warning below that the tab custom view might be null.
3883 assert tabCustomView != null;
3885 // Get the tab views.
3886 ImageView tabFavoriteIconImageView = tabCustomView.findViewById(R.id.favorite_icon_imageview);
3887 TextView tabTitleTextView = tabCustomView.findViewById(R.id.title_textview);
3889 // Set the default favorite icon as the favorite icon for this tab.
3890 tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(nestedScrollWebView.getFavoriteOrDefaultIcon(), 64, 64, true));
3892 // Set the loading title text.
3893 tabTitleTextView.setText(R.string.loading);
3897 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
3898 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
3900 // Get a full cursor from `domainsDatabaseHelper`.
3901 Cursor domainNameCursor = domainsDatabaseHelper.getDomainNameCursorOrderedByDomain();
3903 // Initialize `domainSettingsSet`.
3904 Set<String> domainSettingsSet = new HashSet<>();
3906 // Get the domain name column index.
3907 int domainNameColumnIndex = domainNameCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME);
3909 // Populate `domainSettingsSet`.
3910 for (int i = 0; i < domainNameCursor.getCount(); i++) {
3911 // Move `domainsCursor` to the current row.
3912 domainNameCursor.moveToPosition(i);
3914 // Store the domain name in `domainSettingsSet`.
3915 domainSettingsSet.add(domainNameCursor.getString(domainNameColumnIndex));
3918 // Close `domainNameCursor.
3919 domainNameCursor.close();
3921 // Initialize the domain name in database variable.
3922 String domainNameInDatabase = null;
3924 // Check the hostname against the domain settings set.
3925 if (domainSettingsSet.contains(newHostName)) { // The hostname is contained in the domain settings set.
3926 // Record the domain name in the database.
3927 domainNameInDatabase = newHostName;
3929 // Set the domain settings applied tracker to true.
3930 nestedScrollWebView.setDomainSettingsApplied(true);
3931 } else { // The hostname is not contained in the domain settings set.
3932 // Set the domain settings applied tracker to false.
3933 nestedScrollWebView.setDomainSettingsApplied(false);
3936 // Check all the subdomains of the host name against wildcard domains in the domain cursor.
3937 while (!nestedScrollWebView.getDomainSettingsApplied() && newHostName.contains(".")) { // Stop checking if domain settings are already applied or there are no more `.` in the host name.
3938 if (domainSettingsSet.contains("*." + newHostName)) { // Check the host name prepended by `*.`.
3939 // Set the domain settings applied tracker to true.
3940 nestedScrollWebView.setDomainSettingsApplied(true);
3942 // Store the applied domain names as it appears in the database.
3943 domainNameInDatabase = "*." + newHostName;
3946 // Strip out the lowest subdomain of of the host name.
3947 newHostName = newHostName.substring(newHostName.indexOf(".") + 1);
3951 // Get a handle for the shared preferences.
3952 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
3954 // Store the general preference information.
3955 String defaultFontSizeString = sharedPreferences.getString("font_size", getString(R.string.font_size_default_value));
3956 String defaultUserAgentName = sharedPreferences.getString("user_agent", getString(R.string.user_agent_default_value));
3957 boolean defaultSwipeToRefresh = sharedPreferences.getBoolean("swipe_to_refresh", true);
3958 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
3959 boolean wideViewport = sharedPreferences.getBoolean("wide_viewport", true);
3960 boolean displayWebpageImages = sharedPreferences.getBoolean("display_webpage_images", true);
3962 // Get a handle for the cookie manager.
3963 CookieManager cookieManager = CookieManager.getInstance();
3965 // Get handles for the views.
3966 RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
3967 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
3969 // Initialize the user agent array adapter and string array.
3970 ArrayAdapter<CharSequence> userAgentNamesArray = ArrayAdapter.createFromResource(this, R.array.user_agent_names, R.layout.spinner_item);
3971 String[] userAgentDataArray = getResources().getStringArray(R.array.user_agent_data);
3973 if (nestedScrollWebView.getDomainSettingsApplied()) { // The url has custom domain settings.
3974 // Get a cursor for the current host and move it to the first position.
3975 Cursor currentDomainSettingsCursor = domainsDatabaseHelper.getCursorForDomainName(domainNameInDatabase);
3976 currentDomainSettingsCursor.moveToFirst();
3978 // Get the settings from the cursor.
3979 nestedScrollWebView.setDomainSettingsDatabaseId(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper._ID)));
3980 nestedScrollWebView.setDomainSettingsJavaScriptEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT)) == 1);
3981 nestedScrollWebView.setAcceptFirstPartyCookies(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES)) == 1);
3982 boolean domainThirdPartyCookiesEnabled = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES)) == 1);
3983 nestedScrollWebView.getSettings().setDomStorageEnabled(currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE)) == 1);
3984 // Form data can be removed once the minimum API >= 26.
3985 boolean saveFormData = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA)) == 1);
3986 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST,
3987 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYLIST)) == 1);
3988 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY,
3989 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_EASYPRIVACY)) == 1);
3990 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST,
3991 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_ANNOYANCE_LIST)) == 1);
3992 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST,
3993 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FANBOYS_SOCIAL_BLOCKING_LIST)) == 1);
3994 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ULTRALIST)) == 1);
3995 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY,
3996 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_ULTRAPRIVACY)) == 1);
3997 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS,
3998 currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.BLOCK_ALL_THIRD_PARTY_REQUESTS)) == 1);
3999 String userAgentName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
4000 int fontSize = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
4001 int swipeToRefreshInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SWIPE_TO_REFRESH));
4002 int nightModeInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
4003 int wideViewportInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.WIDE_VIEWPORT));
4004 int displayWebpageImagesInt = currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
4005 boolean pinnedSslCertificate = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
4006 String pinnedSslIssuedToCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
4007 String pinnedSslIssuedToOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
4008 String pinnedSslIssuedToUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATIONAL_UNIT));
4009 String pinnedSslIssuedByCName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_COMMON_NAME));
4010 String pinnedSslIssuedByOName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
4011 String pinnedSslIssuedByUName = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
4012 boolean pinnedIpAddresses = (currentDomainSettingsCursor.getInt(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_IP_ADDRESSES)) == 1);
4013 String pinnedHostIpAddresses = currentDomainSettingsCursor.getString(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.IP_ADDRESSES));
4015 // Create the pinned SSL date variables.
4016 Date pinnedSslStartDate;
4017 Date pinnedSslEndDate;
4019 // 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.
4020 if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
4021 pinnedSslStartDate = null;
4023 pinnedSslStartDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)));
4026 // 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.
4027 if (currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)) == 0) {
4028 pinnedSslEndDate = null;
4030 pinnedSslEndDate = new Date(currentDomainSettingsCursor.getLong(currentDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_END_DATE)));
4033 // If there is a pinned SSL certificate, store it in the WebView.
4034 if (pinnedSslCertificate) {
4035 nestedScrollWebView.setPinnedSslCertificate(pinnedSslIssuedToCName, pinnedSslIssuedToOName, pinnedSslIssuedToUName, pinnedSslIssuedByCName, pinnedSslIssuedByOName, pinnedSslIssuedByUName,
4036 pinnedSslStartDate, pinnedSslEndDate);
4039 // If there is a pinned IP address, store it in the WebView.
4040 if (pinnedIpAddresses) {
4041 nestedScrollWebView.setPinnedIpAddresses(pinnedHostIpAddresses);
4044 // Set night mode according to the night mode int.
4045 switch (nightModeInt) {
4046 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4047 // Set night mode according to the current default.
4048 nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
4051 case DomainsDatabaseHelper.ENABLED:
4052 // Enable night mode.
4053 nestedScrollWebView.setNightMode(true);
4056 case DomainsDatabaseHelper.DISABLED:
4057 // Disable night mode.
4058 nestedScrollWebView.setNightMode(false);
4062 // Enable JavaScript if night mode is enabled.
4063 if (nestedScrollWebView.getNightMode()) {
4064 // Enable JavaScript.
4065 nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
4067 // Set JavaScript according to the domain settings.
4068 nestedScrollWebView.getSettings().setJavaScriptEnabled(nestedScrollWebView.getDomainSettingsJavaScriptEnabled());
4071 // Close the current host domain settings cursor.
4072 currentDomainSettingsCursor.close();
4074 // Apply the domain settings.
4075 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
4077 // Set third-party cookies status if API >= 21.
4078 if (Build.VERSION.SDK_INT >= 21) {
4079 cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, domainThirdPartyCookiesEnabled);
4082 // Apply the form data setting if the API < 26.
4083 if (Build.VERSION.SDK_INT < 26) {
4084 nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
4087 // Apply the font size.
4088 try { // Try the specified font size to see if it is valid.
4089 if (fontSize == 0) { // Apply the default font size.
4090 // Try to set the font size from the value in the app settings.
4091 nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
4092 } else { // Apply the font size from domain settings.
4093 nestedScrollWebView.getSettings().setTextZoom(fontSize);
4095 } catch (Exception exception) { // The specified font size is invalid
4096 // Set the font size to be 100%
4097 nestedScrollWebView.getSettings().setTextZoom(100);
4100 // Set the user agent.
4101 if (userAgentName.equals(getString(R.string.system_default_user_agent))) { // Use the system default user agent.
4102 // Get the array position of the default user agent name.
4103 int defaultUserAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4105 // Set the user agent according to the system default.
4106 switch (defaultUserAgentArrayPosition) {
4107 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4108 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4109 nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
4112 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4113 // Set the user agent to `""`, which uses the default value.
4114 nestedScrollWebView.getSettings().setUserAgentString("");
4117 case SETTINGS_CUSTOM_USER_AGENT:
4118 // Set the default custom user agent.
4119 nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
4123 // Get the user agent string from the user agent data array
4124 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[defaultUserAgentArrayPosition]);
4126 } else { // Set the user agent according to the stored name.
4127 // Get the array position of the user agent name.
4128 int userAgentArrayPosition = userAgentNamesArray.getPosition(userAgentName);
4130 switch (userAgentArrayPosition) {
4131 case UNRECOGNIZED_USER_AGENT: // The user agent name contains a custom user agent.
4132 nestedScrollWebView.getSettings().setUserAgentString(userAgentName);
4135 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4136 // Set the user agent to `""`, which uses the default value.
4137 nestedScrollWebView.getSettings().setUserAgentString("");
4141 // Get the user agent string from the user agent data array.
4142 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4146 // Set swipe to refresh.
4147 switch (swipeToRefreshInt) {
4148 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4149 // Store the swipe to refresh status in the nested scroll WebView.
4150 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
4152 // 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.
4153 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4156 case DomainsDatabaseHelper.ENABLED:
4157 // Store the swipe to refresh status in the nested scroll WebView.
4158 nestedScrollWebView.setSwipeToRefresh(true);
4160 // Enable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
4161 swipeRefreshLayout.setEnabled(true);
4164 case DomainsDatabaseHelper.DISABLED:
4165 // Store the swipe to refresh status in the nested scroll WebView.
4166 nestedScrollWebView.setSwipeToRefresh(false);
4168 // Disable swipe to refresh. This can be removed once the minimum API >= 23 because it is continuously set by an on scroll change listener.
4169 swipeRefreshLayout.setEnabled(false);
4172 // Set the viewport.
4173 switch (wideViewportInt) {
4174 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4175 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4178 case DomainsDatabaseHelper.ENABLED:
4179 nestedScrollWebView.getSettings().setUseWideViewPort(true);
4182 case DomainsDatabaseHelper.DISABLED:
4183 nestedScrollWebView.getSettings().setUseWideViewPort(false);
4187 // Set the loading of webpage images.
4188 switch (displayWebpageImagesInt) {
4189 case DomainsDatabaseHelper.SYSTEM_DEFAULT:
4190 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4193 case DomainsDatabaseHelper.ENABLED:
4194 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(true);
4197 case DomainsDatabaseHelper.DISABLED:
4198 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(false);
4202 // 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.
4204 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
4206 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
4208 } else { // The new URL does not have custom domain settings. Load the defaults.
4209 // Store the values from the shared preferences.
4210 boolean defaultJavaScriptEnabled = sharedPreferences.getBoolean("javascript", false);
4211 nestedScrollWebView.setAcceptFirstPartyCookies(sharedPreferences.getBoolean("first_party_cookies", false));
4212 boolean defaultThirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies", false);
4213 nestedScrollWebView.getSettings().setDomStorageEnabled(sharedPreferences.getBoolean("dom_storage", false));
4214 boolean saveFormData = sharedPreferences.getBoolean("save_form_data", false); // Form data can be removed once the minimum API >= 26.
4215 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYLIST, sharedPreferences.getBoolean("easylist", true));
4216 nestedScrollWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, sharedPreferences.getBoolean("easyprivacy", true));
4217 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, sharedPreferences.getBoolean("fanboys_annoyance_list", true));
4218 nestedScrollWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, sharedPreferences.getBoolean("fanboys_social_blocking_list", true));
4219 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, sharedPreferences.getBoolean("ultralist", true));
4220 nestedScrollWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, sharedPreferences.getBoolean("ultraprivacy", true));
4221 nestedScrollWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, sharedPreferences.getBoolean("block_all_third_party_requests", false));
4222 nestedScrollWebView.setNightMode(sharedPreferences.getBoolean("night_mode", false));
4224 // Enable JavaScript if night mode is enabled.
4225 if (nestedScrollWebView.getNightMode()) {
4226 // Enable JavaScript.
4227 nestedScrollWebView.getSettings().setJavaScriptEnabled(true);
4229 // Set JavaScript according to the domain settings.
4230 nestedScrollWebView.getSettings().setJavaScriptEnabled(defaultJavaScriptEnabled);
4233 // Apply the default first-party cookie setting.
4234 cookieManager.setAcceptCookie(nestedScrollWebView.getAcceptFirstPartyCookies());
4236 // Apply the default font size setting.
4238 // Try to set the font size from the value in the app settings.
4239 nestedScrollWebView.getSettings().setTextZoom(Integer.parseInt(defaultFontSizeString));
4240 } catch (Exception exception) {
4241 // If the app settings value is invalid, set the font size to 100%.
4242 nestedScrollWebView.getSettings().setTextZoom(100);
4245 // Apply the form data setting if the API < 26.
4246 if (Build.VERSION.SDK_INT < 26) {
4247 nestedScrollWebView.getSettings().setSaveFormData(saveFormData);
4250 // Store the swipe to refresh status in the nested scroll WebView.
4251 nestedScrollWebView.setSwipeToRefresh(defaultSwipeToRefresh);
4253 // Apply swipe to refresh according to the default.
4254 swipeRefreshLayout.setEnabled(defaultSwipeToRefresh);
4256 // Reset the pinned variables.
4257 nestedScrollWebView.setDomainSettingsDatabaseId(-1);
4259 // Set third-party cookies status if API >= 21.
4260 if (Build.VERSION.SDK_INT >= 21) {
4261 cookieManager.setAcceptThirdPartyCookies(nestedScrollWebView, defaultThirdPartyCookiesEnabled);
4264 // Get the array position of the user agent name.
4265 int userAgentArrayPosition = userAgentNamesArray.getPosition(defaultUserAgentName);
4267 // Set the user agent.
4268 switch (userAgentArrayPosition) {
4269 case UNRECOGNIZED_USER_AGENT: // The default user agent name is not on the canonical list.
4270 // This is probably because it was set in an older version of Privacy Browser before the switch to persistent user agent names.
4271 nestedScrollWebView.getSettings().setUserAgentString(defaultUserAgentName);
4274 case SETTINGS_WEBVIEW_DEFAULT_USER_AGENT:
4275 // Set the user agent to `""`, which uses the default value.
4276 nestedScrollWebView.getSettings().setUserAgentString("");
4279 case SETTINGS_CUSTOM_USER_AGENT:
4280 // Set the default custom user agent.
4281 nestedScrollWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
4285 // Get the user agent string from the user agent data array
4286 nestedScrollWebView.getSettings().setUserAgentString(userAgentDataArray[userAgentArrayPosition]);
4289 // Set the viewport.
4290 nestedScrollWebView.getSettings().setUseWideViewPort(wideViewport);
4292 // Set the loading of webpage images.
4293 nestedScrollWebView.getSettings().setLoadsImagesAutomatically(displayWebpageImages);
4295 // Set a transparent background on URL edit text. The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21.
4296 urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
4299 // Close the domains database helper.
4300 domainsDatabaseHelper.close();
4302 // Update the privacy icons.
4303 updatePrivacyIcons(true);
4306 // Reload the website if returning from the Domains activity.
4307 if (reloadWebsite) {
4308 nestedScrollWebView.reload();
4311 // Return the user agent changed status.
4312 return !nestedScrollWebView.getSettings().getUserAgentString().equals(initialUserAgent);
4315 private void applyProxy(boolean reloadWebViews) {
4316 // Get a handle for the shared preferences.
4317 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4319 // Get the theme preferences.
4320 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
4322 // Get a handle for the app bar layout.
4323 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
4325 // Set the proxy according to the mode. `this` refers to the current activity where an alert dialog might be displayed.
4326 ProxyHelper.setProxy(getApplicationContext(), appBarLayout, proxyMode);
4328 // Reset the waiting for proxy tracker.
4329 waitingForProxy = false;
4331 // Update the user interface and reload the WebViews if requested.
4332 switch (proxyMode) {
4333 case ProxyHelper.NONE:
4334 // Set the default app bar layout background.
4336 appBarLayout.setBackgroundResource(R.color.gray_900);
4338 appBarLayout.setBackgroundResource(R.color.gray_100);
4342 case ProxyHelper.TOR:
4343 // Set the app bar background to indicate proxying through Orbot is enabled.
4345 appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4347 appBarLayout.setBackgroundResource(R.color.blue_50);
4350 // Check to see if Orbot is installed.
4352 // Get the package manager.
4353 PackageManager packageManager = getPackageManager();
4355 // 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.
4356 packageManager.getPackageInfo("org.torproject.android", 0);
4358 // Check to see if the proxy is ready.
4359 if (!orbotStatus.equals("ON")) { // Orbot is not ready.
4360 // Set the waiting for proxy status.
4361 waitingForProxy = true;
4363 // Show the waiting for proxy dialog if it isn't already displayed.
4364 if (getSupportFragmentManager().findFragmentByTag(getString(R.string.waiting_for_proxy_dialog)) == null) {
4365 // Get a handle for the waiting for proxy alert dialog.
4366 DialogFragment waitingForProxyDialogFragment = new WaitingForProxyDialog();
4368 // Display the waiting for proxy alert dialog.
4369 waitingForProxyDialogFragment.show(getSupportFragmentManager(), getString(R.string.waiting_for_proxy_dialog));
4372 } catch (PackageManager.NameNotFoundException exception) { // Orbot is not installed.
4373 // Show the Orbot not installed dialog if it is not already displayed.
4374 if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4375 // Get a handle for the Orbot not installed alert dialog.
4376 DialogFragment orbotNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4378 // Display the Orbot not installed alert dialog.
4379 orbotNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4384 case ProxyHelper.I2P:
4385 // Set the app bar background to indicate proxying through Orbot is enabled.
4387 appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4389 appBarLayout.setBackgroundResource(R.color.blue_50);
4392 // Check to see if I2P is installed.
4394 // Get the package manager.
4395 PackageManager packageManager = getPackageManager();
4397 // 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.
4398 packageManager.getPackageInfo("org.torproject.android", 0);
4399 } catch (PackageManager.NameNotFoundException exception) { // I2P is not installed.
4400 // Sow the I2P not installed dialog if it is not already displayed.
4401 if (getSupportFragmentManager().findFragmentByTag(getString(R.string.proxy_not_installed_dialog)) == null) {
4402 // Get a handle for the waiting for proxy alert dialog.
4403 DialogFragment i2pNotInstalledDialogFragment = ProxyNotInstalledDialog.displayDialog(proxyMode);
4405 // Display the I2P not installed alert dialog.
4406 i2pNotInstalledDialogFragment.show(getSupportFragmentManager(), getString(R.string.proxy_not_installed_dialog));
4411 case ProxyHelper.CUSTOM:
4412 // Set the app bar background to indicate proxying through Orbot is enabled.
4414 appBarLayout.setBackgroundResource(R.color.dark_blue_30);
4416 appBarLayout.setBackgroundResource(R.color.blue_50);
4421 // Reload the WebViews if requested and not waiting for the proxy.
4422 if (reloadWebViews && !waitingForProxy) {
4423 // Reload the WebViews.
4424 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4425 // Get the WebView tab fragment.
4426 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4428 // Get the fragment view.
4429 View fragmentView = webViewTabFragment.getView();
4431 // Only reload the WebViews if they exist.
4432 if (fragmentView != null) {
4433 // Get the nested scroll WebView from the tab fragment.
4434 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4436 // Reload the WebView.
4437 nestedScrollWebView.reload();
4443 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
4444 // Only update the privacy icons if the options menu and the current WebView have already been populated.
4445 if ((optionsMenu != null) && (currentWebView != null)) {
4446 // Get a handle for the shared preferences.
4447 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4449 // Get the theme and screenshot preferences.
4450 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
4452 // Get handles for the menu items.
4453 MenuItem privacyMenuItem = optionsMenu.findItem(R.id.toggle_javascript);
4454 MenuItem firstPartyCookiesMenuItem = optionsMenu.findItem(R.id.toggle_first_party_cookies);
4455 MenuItem domStorageMenuItem = optionsMenu.findItem(R.id.toggle_dom_storage);
4456 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
4458 // Update the privacy icon.
4459 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled.
4460 privacyMenuItem.setIcon(R.drawable.javascript_enabled);
4461 } else if (currentWebView.getAcceptFirstPartyCookies()) { // JavaScript is disabled but cookies are enabled.
4462 privacyMenuItem.setIcon(R.drawable.warning);
4463 } else { // All the dangerous features are disabled.
4464 privacyMenuItem.setIcon(R.drawable.privacy_mode);
4467 // Update the first-party cookies icon.
4468 if (currentWebView.getAcceptFirstPartyCookies()) { // First-party cookies are enabled.
4469 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_enabled);
4470 } else { // First-party cookies are disabled.
4472 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_dark);
4474 firstPartyCookiesMenuItem.setIcon(R.drawable.cookies_disabled_light);
4478 // Update the DOM storage icon.
4479 if (currentWebView.getSettings().getJavaScriptEnabled() && currentWebView.getSettings().getDomStorageEnabled()) { // Both JavaScript and DOM storage are enabled.
4480 domStorageMenuItem.setIcon(R.drawable.dom_storage_enabled);
4481 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is enabled but DOM storage is disabled.
4483 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_dark);
4485 domStorageMenuItem.setIcon(R.drawable.dom_storage_disabled_light);
4487 } else { // JavaScript is disabled, so DOM storage is ghosted.
4489 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_dark);
4491 domStorageMenuItem.setIcon(R.drawable.dom_storage_ghosted_light);
4495 // Update the refresh icon.
4497 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
4499 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
4502 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
4503 if (runInvalidateOptionsMenu) {
4504 invalidateOptionsMenu();
4509 private void highlightUrlText() {
4510 // Get a handle for the URL edit text.
4511 EditText urlEditText = findViewById(R.id.url_edittext);
4513 // Only highlight the URL text if the box is not currently selected.
4514 if (!urlEditText.hasFocus()) {
4515 // Get the URL string.
4516 String urlString = urlEditText.getText().toString();
4518 // Highlight the URL according to the protocol.
4519 if (urlString.startsWith("file://") || urlString.startsWith("content://")) { // This is a file or content URL.
4520 // De-emphasize everything before the file name.
4521 urlEditText.getText().setSpan(initialGrayColorSpan, 0, urlString.lastIndexOf("/") + 1,Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4522 } else { // This is a web URL.
4523 // Get the index of the `/` immediately after the domain name.
4524 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
4526 // Create a base URL string.
4529 // Get the base URL.
4530 if (endOfDomainName > 0) { // There is at least one character after the base URL.
4531 // Get the base URL.
4532 baseUrl = urlString.substring(0, endOfDomainName);
4533 } else { // There are no characters after the base URL.
4534 // Set the base URL to be the entire URL string.
4535 baseUrl = urlString;
4538 // Get the index of the last `.` in the domain.
4539 int lastDotIndex = baseUrl.lastIndexOf(".");
4541 // Get the index of the penultimate `.` in the domain.
4542 int penultimateDotIndex = baseUrl.lastIndexOf(".", lastDotIndex - 1);
4544 // Markup the beginning of the URL.
4545 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
4546 urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4548 // De-emphasize subdomains.
4549 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4550 urlEditText.getText().setSpan(initialGrayColorSpan, 7, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4552 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
4553 if (penultimateDotIndex > 0) { // There is more than one subdomain in the domain name.
4554 // De-emphasize the protocol and the additional subdomains.
4555 urlEditText.getText().setSpan(initialGrayColorSpan, 0, penultimateDotIndex + 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4556 } else { // There is only one subdomain in the domain name.
4557 // De-emphasize only the protocol.
4558 urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4562 // De-emphasize the text after the domain name.
4563 if (endOfDomainName > 0) {
4564 urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4570 private void loadBookmarksFolder() {
4571 // Update the bookmarks cursor with the contents of the bookmarks database for the current folder.
4572 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
4574 // Populate the bookmarks cursor adapter. `this` specifies the `Context`. `false` disables `autoRequery`.
4575 bookmarksCursorAdapter = new CursorAdapter(this, bookmarksCursor, false) {
4577 public View newView(Context context, Cursor cursor, ViewGroup parent) {
4578 // Inflate the individual item layout. `false` does not attach it to the root.
4579 return getLayoutInflater().inflate(R.layout.bookmarks_drawer_item_linearlayout, parent, false);
4583 public void bindView(View view, Context context, Cursor cursor) {
4584 // Get handles for the views.
4585 ImageView bookmarkFavoriteIcon = view.findViewById(R.id.bookmark_favorite_icon);
4586 TextView bookmarkNameTextView = view.findViewById(R.id.bookmark_name);
4588 // Get the favorite icon byte array from the cursor.
4589 byte[] favoriteIconByteArray = cursor.getBlob(cursor.getColumnIndex(BookmarksDatabaseHelper.FAVORITE_ICON));
4591 // Convert the byte array to a `Bitmap` beginning at the first byte and ending at the last.
4592 Bitmap favoriteIconBitmap = BitmapFactory.decodeByteArray(favoriteIconByteArray, 0, favoriteIconByteArray.length);
4594 // Display the bitmap in `bookmarkFavoriteIcon`.
4595 bookmarkFavoriteIcon.setImageBitmap(favoriteIconBitmap);
4597 // Get the bookmark name from the cursor and display it in `bookmarkNameTextView`.
4598 String bookmarkNameString = cursor.getString(cursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
4599 bookmarkNameTextView.setText(bookmarkNameString);
4601 // Make the font bold for folders.
4602 if (cursor.getInt(cursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) {
4603 bookmarkNameTextView.setTypeface(Typeface.DEFAULT_BOLD);
4604 } else { // Reset the font to default for normal bookmarks.
4605 bookmarkNameTextView.setTypeface(Typeface.DEFAULT);
4610 // Get a handle for the bookmarks list view.
4611 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
4613 // Populate the list view with the adapter.
4614 bookmarksListView.setAdapter(bookmarksCursorAdapter);
4616 // Get a handle for the bookmarks title text view.
4617 TextView bookmarksTitleTextView = findViewById(R.id.bookmarks_title_textview);
4619 // Set the bookmarks drawer title.
4620 if (currentBookmarksFolder.isEmpty()) {
4621 bookmarksTitleTextView.setText(R.string.bookmarks);
4623 bookmarksTitleTextView.setText(currentBookmarksFolder);
4627 private void openWithApp(String url) {
4628 // Create an open with app intent with `ACTION_VIEW`.
4629 Intent openWithAppIntent = new Intent(Intent.ACTION_VIEW);
4631 // Set the URI but not the MIME type. This should open all available apps.
4632 openWithAppIntent.setData(Uri.parse(url));
4634 // Flag the intent to open in a new task.
4635 openWithAppIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4639 // Show the chooser.
4640 startActivity(openWithAppIntent);
4641 } catch (ActivityNotFoundException exception) { // There are no apps available to open the URL.
4642 // Show a snackbar with the error.
4643 Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
4647 private void openWithBrowser(String url) {
4648 // Create an open with browser intent with `ACTION_VIEW`.
4649 Intent openWithBrowserIntent = new Intent(Intent.ACTION_VIEW);
4651 // Set the URI and the MIME type. `"text/html"` should load browser options.
4652 openWithBrowserIntent.setDataAndType(Uri.parse(url), "text/html");
4654 // Flag the intent to open in a new task.
4655 openWithBrowserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
4659 // Show the chooser.
4660 startActivity(openWithBrowserIntent);
4661 } catch (ActivityNotFoundException exception) { // There are no browsers available to open the URL.
4662 // Show a snackbar with the error.
4663 Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
4667 private String sanitizeUrl(String url) {
4668 // Sanitize Google Analytics.
4669 if (sanitizeGoogleAnalytics) {
4671 if (url.contains("?utm_")) {
4672 url = url.substring(0, url.indexOf("?utm_"));
4676 if (url.contains("&utm_")) {
4677 url = url.substring(0, url.indexOf("&utm_"));
4681 // Sanitize Facebook Click IDs.
4682 if (sanitizeFacebookClickIds) {
4683 // Remove `?fbclid=`.
4684 if (url.contains("?fbclid=")) {
4685 url = url.substring(0, url.indexOf("?fbclid="));
4688 // Remove `&fbclid=`.
4689 if (url.contains("&fbclid=")) {
4690 url = url.substring(0, url.indexOf("&fbclid="));
4693 // Remove `?fbadid=`.
4694 if (url.contains("?fbadid=")) {
4695 url = url.substring(0, url.indexOf("?fbadid="));
4698 // Remove `&fbadid=`.
4699 if (url.contains("&fbadid=")) {
4700 url = url.substring(0, url.indexOf("&fbadid="));
4704 // Sanitize Twitter AMP redirects.
4705 if (sanitizeTwitterAmpRedirects) {
4707 if (url.contains("?amp=1")) {
4708 url = url.substring(0, url.indexOf("?amp=1"));
4712 // Return the sanitized URL.
4716 public void finishedPopulatingBlocklists(ArrayList<ArrayList<List<String[]>>> combinedBlocklists) {
4717 // Store the blocklists.
4718 easyList = combinedBlocklists.get(0);
4719 easyPrivacy = combinedBlocklists.get(1);
4720 fanboysAnnoyanceList = combinedBlocklists.get(2);
4721 fanboysSocialList = combinedBlocklists.get(3);
4722 ultraList = combinedBlocklists.get(4);
4723 ultraPrivacy = combinedBlocklists.get(5);
4725 // Add the first tab.
4726 addNewTab("", true);
4729 public void addTab(View view) {
4730 // Add a new tab with a blank URL.
4731 addNewTab("", true);
4734 private void addNewTab(String url, boolean moveToTab) {
4735 // Sanitize the URL.
4736 url = sanitizeUrl(url);
4738 // Get a handle for the tab layout and the view pager.
4739 TabLayout tabLayout = findViewById(R.id.tablayout);
4740 ViewPager webViewPager = findViewById(R.id.webviewpager);
4742 // Get the new page number. The page numbers are 0 indexed, so the new page number will match the current count.
4743 int newTabNumber = tabLayout.getTabCount();
4746 tabLayout.addTab(tabLayout.newTab());
4749 TabLayout.Tab newTab = tabLayout.getTabAt(newTabNumber);
4751 // Remove the lint warning below that the current tab might be null.
4752 assert newTab != null;
4754 // Set a custom view on the new tab.
4755 newTab.setCustomView(R.layout.tab_custom_view);
4757 // Add the new WebView page.
4758 webViewPagerAdapter.addPage(newTabNumber, webViewPager, url, moveToTab);
4761 public void closeTab(View view) {
4762 // Get a handle for the tab layout.
4763 TabLayout tabLayout = findViewById(R.id.tablayout);
4765 // Run the command according to the number of tabs.
4766 if (tabLayout.getTabCount() > 1) { // There is more than one tab open.
4767 // Close the current tab.
4769 } else { // There is only one tab open.
4774 private void closeCurrentTab() {
4775 // Get handles for the views.
4776 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
4777 TabLayout tabLayout = findViewById(R.id.tablayout);
4778 ViewPager webViewPager = findViewById(R.id.webviewpager);
4780 // Get the current tab number.
4781 int currentTabNumber = tabLayout.getSelectedTabPosition();
4783 // Delete the current tab.
4784 tabLayout.removeTabAt(currentTabNumber);
4786 // 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.
4787 if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
4788 setCurrentWebView(currentTabNumber);
4791 // Expand the app bar if it is currently collapsed.
4792 appBarLayout.setExpanded(true);
4795 private void clearAndExit() {
4796 // Get a handle for the shared preferences.
4797 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4799 // Close the bookmarks cursor and database.
4800 bookmarksCursor.close();
4801 bookmarksDatabaseHelper.close();
4803 // Get the status of the clear everything preference.
4804 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
4806 // Get a handle for the runtime.
4807 Runtime runtime = Runtime.getRuntime();
4809 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
4810 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
4811 String privateDataDirectoryString = getApplicationInfo().dataDir;
4814 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
4815 // The command to remove cookies changed slightly in API 21.
4816 if (Build.VERSION.SDK_INT >= 21) {
4817 CookieManager.getInstance().removeAllCookies(null);
4819 CookieManager.getInstance().removeAllCookie();
4822 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4824 // Two commands must be used because `Runtime.exec()` does not like `*`.
4825 Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
4826 Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
4828 // Wait until the processes have finished.
4829 deleteCookiesProcess.waitFor();
4830 deleteCookiesJournalProcess.waitFor();
4831 } catch (Exception exception) {
4832 // Do nothing if an error is thrown.
4836 // Clear DOM storage.
4837 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
4838 // Ask `WebStorage` to clear the DOM storage.
4839 WebStorage webStorage = WebStorage.getInstance();
4840 webStorage.deleteAllData();
4842 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4844 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4845 Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
4847 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
4848 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
4849 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
4850 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
4851 Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
4853 // Wait until the processes have finished.
4854 deleteLocalStorageProcess.waitFor();
4855 deleteIndexProcess.waitFor();
4856 deleteQuotaManagerProcess.waitFor();
4857 deleteQuotaManagerJournalProcess.waitFor();
4858 deleteDatabaseProcess.waitFor();
4859 } catch (Exception exception) {
4860 // Do nothing if an error is thrown.
4864 // Clear form data if the API < 26.
4865 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
4866 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
4867 webViewDatabase.clearFormData();
4869 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
4871 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
4872 Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
4873 Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
4875 // Wait until the processes have finished.
4876 deleteWebDataProcess.waitFor();
4877 deleteWebDataJournalProcess.waitFor();
4878 } catch (Exception exception) {
4879 // Do nothing if an error is thrown.
4884 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
4885 // Clear the cache from each WebView.
4886 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4887 // Get the WebView tab fragment.
4888 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4890 // Get the fragment view.
4891 View fragmentView = webViewTabFragment.getView();
4893 // Only clear the cache if the WebView exists.
4894 if (fragmentView != null) {
4895 // Get the nested scroll WebView from the tab fragment.
4896 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4898 // Clear the cache for this WebView.
4899 nestedScrollWebView.clearCache(true);
4903 // Manually delete the cache directories.
4905 // Delete the main cache directory.
4906 Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
4908 // Delete the secondary `Service Worker` cache directory.
4909 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
4910 Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
4912 // Wait until the processes have finished.
4913 deleteCacheProcess.waitFor();
4914 deleteServiceWorkerProcess.waitFor();
4915 } catch (Exception exception) {
4916 // Do nothing if an error is thrown.
4920 // Wipe out each WebView.
4921 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
4922 // Get the WebView tab fragment.
4923 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
4925 // Get the fragment view.
4926 View fragmentView = webViewTabFragment.getView();
4928 // Only wipe out the WebView if it exists.
4929 if (fragmentView != null) {
4930 // Get the nested scroll WebView from the tab fragment.
4931 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4933 // Clear SSL certificate preferences for this WebView.
4934 nestedScrollWebView.clearSslPreferences();
4936 // Clear the back/forward history for this WebView.
4937 nestedScrollWebView.clearHistory();
4939 // Destroy the internal state of `mainWebView`.
4940 nestedScrollWebView.destroy();
4944 // Clear the custom headers.
4945 customHeaders.clear();
4947 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
4948 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
4949 if (clearEverything) {
4951 // Delete the folder.
4952 Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
4954 // Wait until the process has finished.
4955 deleteAppWebviewProcess.waitFor();
4956 } catch (Exception exception) {
4957 // Do nothing if an error is thrown.
4961 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
4962 if (Build.VERSION.SDK_INT >= 21) {
4963 finishAndRemoveTask();
4968 // Remove the terminated program from RAM. The status code is `0`.
4972 private void setCurrentWebView(int pageNumber) {
4973 // Get a handle for the shared preferences.
4974 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
4976 // Get the theme preference.
4977 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
4979 // Get handles for the URL views.
4980 RelativeLayout urlRelativeLayout = findViewById(R.id.url_relativelayout);
4981 EditText urlEditText = findViewById(R.id.url_edittext);
4982 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
4984 // Stop the swipe to refresh indicator if it is running
4985 swipeRefreshLayout.setRefreshing(false);
4987 // Get the WebView tab fragment.
4988 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(pageNumber);
4990 // Get the fragment view.
4991 View fragmentView = webViewTabFragment.getView();
4993 // Set the current WebView if the fragment view is not null.
4994 if (fragmentView != null) { // The fragment has been populated.
4995 // Store the current WebView.
4996 currentWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
4998 // Update the status of swipe to refresh.
4999 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
5000 // Enable the swipe refresh layout if the WebView is scrolled all the way to the top. It is updated every time the scroll changes.
5001 swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
5002 } else { // Swipe to refresh is disabled.
5003 // Disable the swipe refresh layout.
5004 swipeRefreshLayout.setEnabled(false);
5007 // Get a handle for the cookie manager.
5008 CookieManager cookieManager = CookieManager.getInstance();
5010 // Set the first-party cookie status.
5011 cookieManager.setAcceptCookie(currentWebView.getAcceptFirstPartyCookies());
5013 // Update the privacy icons. `true` redraws the icons in the app bar.
5014 updatePrivacyIcons(true);
5016 // Get a handle for the input method manager.
5017 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5019 // Remove the lint warning below that the input method manager might be null.
5020 assert inputMethodManager != null;
5022 // Get the current URL.
5023 String url = currentWebView.getUrl();
5025 // Update the URL edit text if not loading a new intent. Otherwise, this will be handled by `onPageStarted()` (if called) and `onPageFinished()`.
5026 if (!loadingNewIntent) { // A new intent is not being loaded.
5027 if ((url == null) || url.equals("about:blank")) { // The WebView is blank.
5028 // Display the hint in the URL edit text.
5029 urlEditText.setText("");
5031 // Request focus for the URL text box.
5032 urlEditText.requestFocus();
5034 // Display the keyboard.
5035 inputMethodManager.showSoftInput(urlEditText, 0);
5036 } else { // The WebView has a loaded URL.
5037 // Clear the focus from the URL text box.
5038 urlEditText.clearFocus();
5040 // Hide the soft keyboard.
5041 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
5043 // Display the current URL in the URL text box.
5044 urlEditText.setText(url);
5046 // Highlight the URL text.
5049 } else { // A new intent is being loaded.
5050 // Reset the loading new intent tracker.
5051 loadingNewIntent = false;
5054 // Set the background to indicate the domain settings status.
5055 if (currentWebView.getDomainSettingsApplied()) {
5056 // 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.
5058 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_dark_blue));
5060 urlRelativeLayout.setBackground(getResources().getDrawable(R.drawable.url_bar_background_light_green));
5063 urlRelativeLayout.setBackground(getResources().getDrawable(R.color.transparent));
5065 } else { // The fragment has not been populated. Try again in 100 milliseconds.
5066 // Create a handler to set the current WebView.
5067 Handler setCurrentWebViewHandler = new Handler();
5069 // Create a runnable to set the current WebView.
5070 Runnable setCurrentWebWebRunnable = () -> {
5071 // Set the current WebView.
5072 setCurrentWebView(pageNumber);
5075 // Try setting the current WebView again after 100 milliseconds.
5076 setCurrentWebViewHandler.postDelayed(setCurrentWebWebRunnable, 100);
5081 public void initializeWebView(NestedScrollWebView nestedScrollWebView, int pageNumber, ProgressBar progressBar, String url) {
5082 // Get handles for the activity views.
5083 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
5084 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
5085 RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
5086 ActionBar actionBar = getSupportActionBar();
5087 LinearLayout tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
5088 EditText urlEditText = findViewById(R.id.url_edittext);
5089 TabLayout tabLayout = findViewById(R.id.tablayout);
5090 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
5092 // Remove the incorrect lint warning below that the action bar might be null.
5093 assert actionBar != null;
5095 // Get a handle for the activity
5096 Activity activity = this;
5098 // Get a handle for the input method manager.
5099 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
5101 // Instantiate the blocklist helper.
5102 BlocklistHelper blocklistHelper = new BlocklistHelper();
5104 // Remove the lint warning below that the input method manager might be null.
5105 assert inputMethodManager != null;
5107 // Get a handle for the shared preferences.
5108 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
5110 // Initialize the favorite icon.
5111 nestedScrollWebView.initializeFavoriteIcon();
5113 // Set the app bar scrolling.
5114 nestedScrollWebView.setNestedScrollingEnabled(sharedPreferences.getBoolean("scroll_app_bar", true));
5116 // Allow pinch to zoom.
5117 nestedScrollWebView.getSettings().setBuiltInZoomControls(true);
5119 // Hide zoom controls.
5120 nestedScrollWebView.getSettings().setDisplayZoomControls(false);
5122 // Don't allow mixed content (HTTP and HTTPS) on the same website.
5123 if (Build.VERSION.SDK_INT >= 21) {
5124 nestedScrollWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
5127 // Set the WebView to load in overview mode (zoomed out to the maximum width).
5128 nestedScrollWebView.getSettings().setLoadWithOverviewMode(true);
5130 // Explicitly disable geolocation.
5131 nestedScrollWebView.getSettings().setGeolocationEnabled(false);
5133 // Create a double-tap gesture detector to toggle full-screen mode.
5134 GestureDetector doubleTapGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.SimpleOnGestureListener() {
5135 // Override `onDoubleTap()`. All other events are handled using the default settings.
5137 public boolean onDoubleTap(MotionEvent event) {
5138 if (fullScreenBrowsingModeEnabled) { // Only process the double-tap if full screen browsing mode is enabled.
5139 // Toggle the full screen browsing mode tracker.
5140 inFullScreenBrowsingMode = !inFullScreenBrowsingMode;
5142 // Toggle the full screen browsing mode.
5143 if (inFullScreenBrowsingMode) { // Switch to full screen mode.
5144 // Store the swipe refresh layout top padding.
5145 swipeRefreshLayoutPaddingTop = swipeRefreshLayout.getPaddingTop();
5147 // Hide the app bar if specified.
5149 // Close the find on page bar if it is visible.
5150 closeFindOnPage(null);
5152 // Hide the tab linear layout.
5153 tabsLinearLayout.setVisibility(View.GONE);
5155 // Hide the action bar.
5158 // Check to see if app bar scrolling is disabled.
5159 if (!scrollAppBar) {
5160 // Remove the padding from the top of the swipe refresh layout.
5161 swipeRefreshLayout.setPadding(0, 0, 0, 0);
5165 // Hide the banner ad in the free flavor.
5166 if (BuildConfig.FLAVOR.contentEquals("free")) {
5167 AdHelper.hideAd(findViewById(R.id.adview));
5170 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
5171 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5173 /* Hide the system bars.
5174 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5175 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5176 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5177 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5179 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5180 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5181 } else { // Switch to normal viewing mode.
5182 // Show the tab linear layout.
5183 tabsLinearLayout.setVisibility(View.VISIBLE);
5185 // Show the action bar.
5188 // Check to see if app bar scrolling is disabled.
5189 if (!scrollAppBar) {
5190 // Add the padding from the top of the swipe refresh layout.
5191 swipeRefreshLayout.setPadding(0, swipeRefreshLayoutPaddingTop, 0, 0);
5194 // Show the banner ad in the free flavor.
5195 if (BuildConfig.FLAVOR.contentEquals("free")) {
5197 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
5200 // Remove the `SYSTEM_UI` flags from the root frame layout.
5201 rootFrameLayout.setSystemUiVisibility(0);
5203 // Add the translucent status flag.
5204 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5207 // Consume the double-tap.
5209 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
5215 // Pass all touch events on the WebView through the double-tap gesture detector.
5216 nestedScrollWebView.setOnTouchListener((View view, MotionEvent event) -> {
5217 // Call `performClick()` on the view, which is required for accessibility.
5218 view.performClick();
5220 // Send the event to the gesture detector.
5221 return doubleTapGestureDetector.onTouchEvent(event);
5224 // Register the WebView for a context menu. This is used to see link targets and download images.
5225 registerForContextMenu(nestedScrollWebView);
5227 // Allow the downloading of files.
5228 nestedScrollWebView.setDownloadListener((String downloadUrl, String userAgent, String contentDisposition, String mimetype, long contentLength) -> {
5229 // Instantiate the save dialog.
5230 DialogFragment saveDialogFragment = SaveDialog.saveUrl(StoragePermissionDialog.SAVE_URL, downloadUrl, userAgent, nestedScrollWebView.getAcceptFirstPartyCookies());
5232 // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
5233 saveDialogFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
5236 // Update the find on page count.
5237 nestedScrollWebView.setFindListener(new WebView.FindListener() {
5238 // Get a handle for `findOnPageCountTextView`.
5239 final TextView findOnPageCountTextView = findViewById(R.id.find_on_page_count_textview);
5242 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
5243 if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches.
5244 // Set `findOnPageCountTextView` to `0/0`.
5245 findOnPageCountTextView.setText(R.string.zero_of_zero);
5246 } else if (isDoneCounting) { // There are matches.
5247 // `activeMatchOrdinal` is zero-based.
5248 int activeMatch = activeMatchOrdinal + 1;
5250 // Build the match string.
5251 String matchString = activeMatch + "/" + numberOfMatches;
5253 // Set `findOnPageCountTextView`.
5254 findOnPageCountTextView.setText(matchString);
5259 // Update the status of swipe to refresh based on the scroll position of the nested scroll WebView. Also reinforce full screen browsing mode.
5260 // Once the minimum API >= 23 this can be replaced with `nestedScrollWebView.setOnScrollChangeListener()`.
5261 nestedScrollWebView.getViewTreeObserver().addOnScrollChangedListener(() -> {
5262 if (nestedScrollWebView.getSwipeToRefresh()) {
5263 // Only enable swipe to refresh if the WebView is scrolled to the top.
5264 swipeRefreshLayout.setEnabled(nestedScrollWebView.getScrollY() == 0);
5267 // Reinforce the system UI visibility flags if in full screen browsing mode.
5268 // This hides the status and navigation bars, which are displayed if other elements are shown, like dialog boxes, the options menu, or the keyboard.
5269 if (inFullScreenBrowsingMode) {
5270 /* Hide the system bars.
5271 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5272 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5273 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5274 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5276 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5277 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5281 // Set the web chrome client.
5282 nestedScrollWebView.setWebChromeClient(new WebChromeClient() {
5283 // Update the progress bar when a page is loading.
5285 public void onProgressChanged(WebView view, int progress) {
5286 // Inject the night mode CSS if night mode is enabled.
5287 if (nestedScrollWebView.getNightMode()) { // Night mode is enabled.
5288 // `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
5289 // used by WordPress. `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color.
5290 // `border: none` removes all borders, which can also be used to underline text. `a {color: #1565C0}` sets links to be a dark blue.
5291 // `::selection {background: #0D47A1}' sets the text selection highlight color to be a dark blue. `!important` takes precedent over any existing sub-settings.
5292 nestedScrollWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; " +
5293 "style.innerHTML = '* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important;" +
5294 "text-shadow: none !important; border: none !important;} a {color: #1565C0 !important;} ::selection {background: #0D47A1 !important;}'; parent.appendChild(style)})()", value -> {
5295 // Initialize a handler to display `mainWebView`.
5296 Handler displayWebViewHandler = new Handler();
5298 // Setup a runnable to display `mainWebView` after a delay to allow the CSS to be applied.
5299 Runnable displayWebViewRunnable = () -> {
5300 // Only display `mainWebView` if the progress bar is gone. This prevents the display of the `WebView` while it is still loading.
5301 if (progressBar.getVisibility() == View.GONE) {
5302 nestedScrollWebView.setVisibility(View.VISIBLE);
5306 // Display the WebView after 500 milliseconds.
5307 displayWebViewHandler.postDelayed(displayWebViewRunnable, 500);
5309 } else { // Night mode is disabled.
5310 // Display the nested scroll WebView if night mode is disabled.
5311 // Because of a race condition between `applyDomainSettings` and `onPageStarted`,
5312 // when night mode is set by domain settings the WebView may be hidden even if night mode is not currently enabled.
5313 nestedScrollWebView.setVisibility(View.VISIBLE);
5316 // Update the progress bar.
5317 progressBar.setProgress(progress);
5319 // Set the visibility of the progress bar.
5320 if (progress < 100) {
5321 // Show the progress bar.
5322 progressBar.setVisibility(View.VISIBLE);
5324 // Hide the progress bar.
5325 progressBar.setVisibility(View.GONE);
5327 //Stop the swipe to refresh indicator if it is running
5328 swipeRefreshLayout.setRefreshing(false);
5332 // Set the favorite icon when it changes.
5334 public void onReceivedIcon(WebView view, Bitmap icon) {
5335 // Only update the favorite icon if the website has finished loading.
5336 if (progressBar.getVisibility() == View.GONE) {
5337 // Store the new favorite icon.
5338 nestedScrollWebView.setFavoriteOrDefaultIcon(icon);
5340 // Get the current page position.
5341 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5343 // Get the current tab.
5344 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5346 // Check to see if the tab has been populated.
5348 // Get the custom view from the tab.
5349 View tabView = tab.getCustomView();
5351 // Check to see if the custom tab view has been populated.
5352 if (tabView != null) {
5353 // Get the favorite icon image view from the tab.
5354 ImageView tabFavoriteIconImageView = tabView.findViewById(R.id.favorite_icon_imageview);
5356 // Display the favorite icon in the tab.
5357 tabFavoriteIconImageView.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
5363 // Save a copy of the title when it changes.
5365 public void onReceivedTitle(WebView view, String title) {
5366 // Get the current page position.
5367 int currentPosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5369 // Get the current tab.
5370 TabLayout.Tab tab = tabLayout.getTabAt(currentPosition);
5372 // Only populate the title text view if the tab has been fully created.
5374 // Get the custom view from the tab.
5375 View tabView = tab.getCustomView();
5377 // Remove the incorrect warning below that the current tab view might be null.
5378 assert tabView != null;
5380 // Get the title text view from the tab.
5381 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
5383 // Set the title according to the URL.
5384 if (title.equals("about:blank")) {
5385 // Set the title to indicate a new tab.
5386 tabTitleTextView.setText(R.string.new_tab);
5388 // Set the title as the tab text.
5389 tabTitleTextView.setText(title);
5394 // Enter full screen video.
5396 public void onShowCustomView(View video, CustomViewCallback callback) {
5397 // Get a handle for the full screen video frame layout.
5398 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
5400 // Set the full screen video flag.
5401 displayingFullScreenVideo = true;
5403 // Pause the ad if this is the free flavor.
5404 if (BuildConfig.FLAVOR.contentEquals("free")) {
5405 // The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
5406 AdHelper.pauseAd(findViewById(R.id.adview));
5409 // Hide the keyboard.
5410 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
5412 // Hide the main content relative layout.
5413 mainContentRelativeLayout.setVisibility(View.GONE);
5415 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
5416 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
5418 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
5419 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5421 /* Hide the system bars.
5422 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5423 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5424 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5425 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5427 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5428 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5430 // Disable the sliding drawers.
5431 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
5433 // Add the video view to the full screen video frame layout.
5434 fullScreenVideoFrameLayout.addView(video);
5436 // Show the full screen video frame layout.
5437 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
5439 // Disable the screen timeout while the video is playing. YouTube does this automatically, but not all other videos do.
5440 fullScreenVideoFrameLayout.setKeepScreenOn(true);
5443 // Exit full screen video.
5445 public void onHideCustomView() {
5446 // Get a handle for the full screen video frame layout.
5447 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
5449 // Re-enable the screen timeout.
5450 fullScreenVideoFrameLayout.setKeepScreenOn(false);
5452 // Unset the full screen video flag.
5453 displayingFullScreenVideo = false;
5455 // Remove all the views from the full screen video frame layout.
5456 fullScreenVideoFrameLayout.removeAllViews();
5458 // Hide the full screen video frame layout.
5459 fullScreenVideoFrameLayout.setVisibility(View.GONE);
5461 // Enable the sliding drawers.
5462 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
5464 // Show the main content relative layout.
5465 mainContentRelativeLayout.setVisibility(View.VISIBLE);
5467 // Apply the appropriate full screen mode flags.
5468 if (fullScreenBrowsingModeEnabled && inFullScreenBrowsingMode) { // Privacy Browser is currently in full screen browsing mode.
5469 // Hide the app bar if specified.
5471 // Hide the tab linear layout.
5472 tabsLinearLayout.setVisibility(View.GONE);
5474 // Hide the action bar.
5478 // Hide the banner ad in the free flavor.
5479 if (BuildConfig.FLAVOR.contentEquals("free")) {
5480 AdHelper.hideAd(findViewById(R.id.adview));
5483 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
5484 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5486 /* Hide the system bars.
5487 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
5488 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
5489 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
5490 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
5492 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
5493 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
5494 } else { // Switch to normal viewing mode.
5495 // Remove the `SYSTEM_UI` flags from the root frame layout.
5496 rootFrameLayout.setSystemUiVisibility(0);
5498 // Add the translucent status flag.
5499 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
5502 // Reload the ad for the free flavor if not in full screen mode.
5503 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
5505 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
5511 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
5512 // Show the file chooser if the device is running API >= 21.
5513 if (Build.VERSION.SDK_INT >= 21) {
5514 // Store the file path callback.
5515 fileChooserCallback = filePathCallback;
5517 // Create an intent to open a chooser based ont the file chooser parameters.
5518 Intent fileChooserIntent = fileChooserParams.createIntent();
5520 // Open the file chooser.
5521 startActivityForResult(fileChooserIntent, BROWSE_FILE_UPLOAD_REQUEST_CODE);
5527 nestedScrollWebView.setWebViewClient(new WebViewClient() {
5528 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
5529 // The deprecated `shouldOverrideUrlLoading` must be used until API >= 24.
5531 public boolean shouldOverrideUrlLoading(WebView view, String url) {
5532 // Sanitize the url.
5533 url = sanitizeUrl(url);
5535 if (url.startsWith("http")) { // Load the URL in Privacy Browser.
5536 // Apply the domain settings for the new URL. This doesn't do anything if the domain has not changed.
5537 boolean userAgentChanged = applyDomainSettings(nestedScrollWebView, url, true, false);
5539 // Check if the user agent has changed.
5540 if (userAgentChanged) {
5541 // Manually load the URL. The changing of the user agent will cause WebView to reload the previous URL.
5542 nestedScrollWebView.loadUrl(url, customHeaders);
5544 // Returning true indicates that Privacy Browser is manually handling the loading of the URL.
5547 // Returning false causes the current WebView to handle the URL and prevents it from adding redirects to the history list.
5550 } else if (url.startsWith("mailto:")) { // Load the email address in an external email program.
5551 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
5552 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
5554 // Parse the url and set it as the data for the intent.
5555 emailIntent.setData(Uri.parse(url));
5557 // Open the email program in a new task instead of as part of Privacy Browser.
5558 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5561 startActivity(emailIntent);
5563 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5565 } else if (url.startsWith("tel:")) { // Load the phone number in the dialer.
5566 // Open the dialer and load the phone number, but wait for the user to place the call.
5567 Intent dialIntent = new Intent(Intent.ACTION_DIAL);
5569 // Add the phone number to the intent.
5570 dialIntent.setData(Uri.parse(url));
5572 // Open the dialer in a new task instead of as part of Privacy Browser.
5573 dialIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5576 startActivity(dialIntent);
5578 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5580 } else { // Load a system chooser to select an app that can handle the URL.
5581 // Open an app that can handle the URL.
5582 Intent genericIntent = new Intent(Intent.ACTION_VIEW);
5584 // Add the URL to the intent.
5585 genericIntent.setData(Uri.parse(url));
5587 // List all apps that can handle the URL instead of just opening the first one.
5588 genericIntent.addCategory(Intent.CATEGORY_BROWSABLE);
5590 // Open the app in a new task instead of as part of Privacy Browser.
5591 genericIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
5593 // Start the app or display a snackbar if no app is available to handle the URL.
5595 startActivity(genericIntent);
5596 } catch (ActivityNotFoundException exception) {
5597 Snackbar.make(nestedScrollWebView, getString(R.string.unrecognized_url) + " " + url, Snackbar.LENGTH_SHORT).show();
5600 // Returning true indicates Privacy Browser is handling the URL by creating an intent.
5605 // Check requests against the block lists. The deprecated `shouldInterceptRequest()` must be used until minimum API >= 21.
5607 public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
5608 // Check to see if the resource request is for the main URL.
5609 if (url.equals(nestedScrollWebView.getCurrentUrl())) {
5610 // `return null` loads the resource request, which should never be blocked if it is the main URL.
5614 // 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.
5615 while (ultraPrivacy == null) {
5616 // The wait must be synchronized, which only lets one thread run on it at a time, or `java.lang.IllegalMonitorStateException` is thrown.
5617 synchronized (this) {
5619 // Check to see if the blocklists have been populated after 100 ms.
5621 } catch (InterruptedException exception) {
5627 // Sanitize the URL.
5628 url = sanitizeUrl(url);
5630 // Get a handle for the navigation view.
5631 NavigationView navigationView = findViewById(R.id.navigationview);
5633 // Get a handle for the navigation menu.
5634 Menu navigationMenu = navigationView.getMenu();
5636 // Get a handle for the navigation requests menu item. The menu is 0 based.
5637 MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
5639 // Create an empty web resource response to be used if the resource request is blocked.
5640 WebResourceResponse emptyWebResourceResponse = new WebResourceResponse("text/plain", "utf8", new ByteArrayInputStream("".getBytes()));
5642 // Reset the whitelist results tracker.
5643 String[] whitelistResultStringArray = null;
5645 // Initialize the third party request tracker.
5646 boolean isThirdPartyRequest = false;
5648 // Get the current URL. `.getUrl()` throws an error because operations on the WebView cannot be made from this thread.
5649 String currentBaseDomain = nestedScrollWebView.getCurrentDomainName();
5651 // Store a copy of the current domain for use in later requests.
5652 String currentDomain = currentBaseDomain;
5654 // Nobody is happy when comparing null strings.
5655 if ((currentBaseDomain != null) && (url != null)) {
5656 // Convert the request URL to a URI.
5657 Uri requestUri = Uri.parse(url);
5659 // Get the request host name.
5660 String requestBaseDomain = requestUri.getHost();
5662 // Only check for third-party requests if the current base domain is not empty and the request domain is not null.
5663 if (!currentBaseDomain.isEmpty() && (requestBaseDomain != null)) {
5664 // Determine the current base domain.
5665 while (currentBaseDomain.indexOf(".", currentBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
5666 // Remove the first subdomain.
5667 currentBaseDomain = currentBaseDomain.substring(currentBaseDomain.indexOf(".") + 1);
5670 // Determine the request base domain.
5671 while (requestBaseDomain.indexOf(".", requestBaseDomain.indexOf(".") + 1) > 0) { // There is at least one subdomain.
5672 // Remove the first subdomain.
5673 requestBaseDomain = requestBaseDomain.substring(requestBaseDomain.indexOf(".") + 1);
5676 // Update the third party request tracker.
5677 isThirdPartyRequest = !currentBaseDomain.equals(requestBaseDomain);
5681 // Get the current WebView page position.
5682 int webViewPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
5684 // Determine if the WebView is currently displayed.
5685 boolean webViewDisplayed = (webViewPagePosition == tabLayout.getSelectedTabPosition());
5687 // Block third-party requests if enabled.
5688 if (isThirdPartyRequest && nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS)) {
5689 // Add the result to the resource requests.
5690 nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_THIRD_PARTY, url});
5692 // Increment the blocked requests counters.
5693 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5694 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS);
5696 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5697 if (webViewDisplayed) {
5698 // Updating the UI must be run from the UI thread.
5699 activity.runOnUiThread(() -> {
5700 // Update the menu item titles.
5701 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5703 // Update the options menu if it has been populated.
5704 if (optionsMenu != null) {
5705 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5706 optionsMenu.findItem(R.id.block_all_third_party_requests).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " +
5707 getString(R.string.block_all_third_party_requests));
5712 // Return an empty web resource response.
5713 return emptyWebResourceResponse;
5716 // Check UltraList if it is enabled.
5717 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST)) {
5718 // Check the URL against UltraList.
5719 String[] ultraListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraList);
5721 // Process the UltraList results.
5722 if (ultraListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraLists's blacklist.
5723 // Add the result to the resource requests.
5724 nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5726 // Increment the blocked requests counters.
5727 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5728 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRALIST);
5730 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5731 if (webViewDisplayed) {
5732 // Updating the UI must be run from the UI thread.
5733 activity.runOnUiThread(() -> {
5734 // Update the menu item titles.
5735 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5737 // Update the options menu if it has been populated.
5738 if (optionsMenu != null) {
5739 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5740 optionsMenu.findItem(R.id.ultralist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
5745 // The resource request was blocked. Return an empty web resource response.
5746 return emptyWebResourceResponse;
5747 } else if (ultraListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraList's whitelist.
5748 // Add a whitelist entry to the resource requests array.
5749 nestedScrollWebView.addResourceRequest(new String[] {ultraListResults[0], ultraListResults[1], ultraListResults[2], ultraListResults[3], ultraListResults[4], ultraListResults[5]});
5751 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
5756 // Check UltraPrivacy if it is enabled.
5757 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY)) {
5758 // Check the URL against UltraPrivacy.
5759 String[] ultraPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, ultraPrivacy);
5761 // Process the UltraPrivacy results.
5762 if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched UltraPrivacy's blacklist.
5763 // Add the result to the resource requests.
5764 nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5765 ultraPrivacyResults[5]});
5767 // Increment the blocked requests counters.
5768 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5769 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.ULTRAPRIVACY);
5771 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5772 if (webViewDisplayed) {
5773 // Updating the UI must be run from the UI thread.
5774 activity.runOnUiThread(() -> {
5775 // Update the menu item titles.
5776 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5778 // Update the options menu if it has been populated.
5779 if (optionsMenu != null) {
5780 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5781 optionsMenu.findItem(R.id.ultraprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
5786 // The resource request was blocked. Return an empty web resource response.
5787 return emptyWebResourceResponse;
5788 } else if (ultraPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched UltraPrivacy's whitelist.
5789 // Add a whitelist entry to the resource requests array.
5790 nestedScrollWebView.addResourceRequest(new String[] {ultraPrivacyResults[0], ultraPrivacyResults[1], ultraPrivacyResults[2], ultraPrivacyResults[3], ultraPrivacyResults[4],
5791 ultraPrivacyResults[5]});
5793 // The resource request has been allowed by UltraPrivacy. `return null` loads the requested resource.
5798 // Check EasyList if it is enabled.
5799 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST)) {
5800 // Check the URL against EasyList.
5801 String[] easyListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyList);
5803 // Process the EasyList results.
5804 if (easyListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyList's blacklist.
5805 // Add the result to the resource requests.
5806 nestedScrollWebView.addResourceRequest(new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]});
5808 // Increment the blocked requests counters.
5809 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5810 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYLIST);
5812 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5813 if (webViewDisplayed) {
5814 // Updating the UI must be run from the UI thread.
5815 activity.runOnUiThread(() -> {
5816 // Update the menu item titles.
5817 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5819 // Update the options menu if it has been populated.
5820 if (optionsMenu != null) {
5821 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5822 optionsMenu.findItem(R.id.easylist).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
5827 // The resource request was blocked. Return an empty web resource response.
5828 return emptyWebResourceResponse;
5829 } else if (easyListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyList's whitelist.
5830 // Update the whitelist result string array tracker.
5831 whitelistResultStringArray = new String[] {easyListResults[0], easyListResults[1], easyListResults[2], easyListResults[3], easyListResults[4], easyListResults[5]};
5835 // Check EasyPrivacy if it is enabled.
5836 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY)) {
5837 // Check the URL against EasyPrivacy.
5838 String[] easyPrivacyResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, easyPrivacy);
5840 // Process the EasyPrivacy results.
5841 if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched EasyPrivacy's blacklist.
5842 // Add the result to the resource requests.
5843 nestedScrollWebView.addResourceRequest(new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4],
5844 easyPrivacyResults[5]});
5846 // Increment the blocked requests counters.
5847 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5848 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.EASYPRIVACY);
5850 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5851 if (webViewDisplayed) {
5852 // Updating the UI must be run from the UI thread.
5853 activity.runOnUiThread(() -> {
5854 // Update the menu item titles.
5855 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5857 // Update the options menu if it has been populated.
5858 if (optionsMenu != null) {
5859 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5860 optionsMenu.findItem(R.id.easyprivacy).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
5865 // The resource request was blocked. Return an empty web resource response.
5866 return emptyWebResourceResponse;
5867 } else if (easyPrivacyResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched EasyPrivacy's whitelist.
5868 // Update the whitelist result string array tracker.
5869 whitelistResultStringArray = new String[] {easyPrivacyResults[0], easyPrivacyResults[1], easyPrivacyResults[2], easyPrivacyResults[3], easyPrivacyResults[4], easyPrivacyResults[5]};
5873 // Check Fanboy’s Annoyance List if it is enabled.
5874 if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST)) {
5875 // Check the URL against Fanboy's Annoyance List.
5876 String[] fanboysAnnoyanceListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysAnnoyanceList);
5878 // Process the Fanboy's Annoyance List results.
5879 if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Annoyance List's blacklist.
5880 // Add the result to the resource requests.
5881 nestedScrollWebView.addResourceRequest(new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5882 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]});
5884 // Increment the blocked requests counters.
5885 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5886 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST);
5888 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5889 if (webViewDisplayed) {
5890 // Updating the UI must be run from the UI thread.
5891 activity.runOnUiThread(() -> {
5892 // Update the menu item titles.
5893 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5895 // Update the options menu if it has been populated.
5896 if (optionsMenu != null) {
5897 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5898 optionsMenu.findItem(R.id.fanboys_annoyance_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " +
5899 getString(R.string.fanboys_annoyance_list));
5904 // The resource request was blocked. Return an empty web resource response.
5905 return emptyWebResourceResponse;
5906 } else if (fanboysAnnoyanceListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)){ // The resource request matched Fanboy's Annoyance List's whitelist.
5907 // Update the whitelist result string array tracker.
5908 whitelistResultStringArray = new String[] {fanboysAnnoyanceListResults[0], fanboysAnnoyanceListResults[1], fanboysAnnoyanceListResults[2], fanboysAnnoyanceListResults[3],
5909 fanboysAnnoyanceListResults[4], fanboysAnnoyanceListResults[5]};
5911 } else if (nestedScrollWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST)) { // Only check Fanboy’s Social Blocking List if Fanboy’s Annoyance List is disabled.
5912 // Check the URL against Fanboy's Annoyance List.
5913 String[] fanboysSocialListResults = blocklistHelper.checkBlocklist(currentDomain, url, isThirdPartyRequest, fanboysSocialList);
5915 // Process the Fanboy's Social Blocking List results.
5916 if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_BLOCKED)) { // The resource request matched Fanboy's Social Blocking List's blacklist.
5917 // Add the result to the resource requests.
5918 nestedScrollWebView.addResourceRequest(new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5919 fanboysSocialListResults[4], fanboysSocialListResults[5]});
5921 // Increment the blocked requests counters.
5922 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS);
5923 nestedScrollWebView.incrementRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST);
5925 // Update the titles of the blocklist menu items if the WebView is currently displayed.
5926 if (webViewDisplayed) {
5927 // Updating the UI must be run from the UI thread.
5928 activity.runOnUiThread(() -> {
5929 // Update the menu item titles.
5930 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5932 // Update the options menu if it has been populated.
5933 if (optionsMenu != null) {
5934 optionsMenu.findItem(R.id.blocklists).setTitle(getString(R.string.blocklists) + " - " + nestedScrollWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
5935 optionsMenu.findItem(R.id.fanboys_social_blocking_list).setTitle(nestedScrollWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " +
5936 getString(R.string.fanboys_social_blocking_list));
5941 // The resource request was blocked. Return an empty web resource response.
5942 return emptyWebResourceResponse;
5943 } else if (fanboysSocialListResults[0].equals(BlocklistHelper.REQUEST_ALLOWED)) { // The resource request matched Fanboy's Social Blocking List's whitelist.
5944 // Update the whitelist result string array tracker.
5945 whitelistResultStringArray = new String[] {fanboysSocialListResults[0], fanboysSocialListResults[1], fanboysSocialListResults[2], fanboysSocialListResults[3],
5946 fanboysSocialListResults[4], fanboysSocialListResults[5]};
5950 // Add the request to the log because it hasn't been processed by any of the previous checks.
5951 if (whitelistResultStringArray != null) { // The request was processed by a whitelist.
5952 nestedScrollWebView.addResourceRequest(whitelistResultStringArray);
5953 } else { // The request didn't match any blocklist entry. Log it as a default request.
5954 nestedScrollWebView.addResourceRequest(new String[]{BlocklistHelper.REQUEST_DEFAULT, url});
5957 // The resource request has not been blocked. `return null` loads the requested resource.
5961 // Handle HTTP authentication requests.
5963 public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
5964 // Store the handler.
5965 nestedScrollWebView.setHttpAuthHandler(handler);
5967 // Instantiate an HTTP authentication dialog.
5968 DialogFragment httpAuthenticationDialogFragment = HttpAuthenticationDialog.displayDialog(host, realm, nestedScrollWebView.getWebViewFragmentId());
5970 // Show the HTTP authentication dialog.
5971 httpAuthenticationDialogFragment.show(getSupportFragmentManager(), getString(R.string.http_authentication));
5975 public void onPageStarted(WebView view, String url, Bitmap favicon) {
5976 // Get the preferences.
5977 boolean scrollAppBar = sharedPreferences.getBoolean("scroll_app_bar", true);
5978 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
5980 // Get a handler for the app bar layout.
5981 AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);
5983 // Set the top padding of the swipe refresh layout according to the app bar scrolling preference.
5985 // No padding is needed because it will automatically be placed below the app bar layout due to the scrolling layout behavior.
5986 swipeRefreshLayout.setPadding(0, 0, 0, 0);
5988 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5989 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10, defaultProgressViewEndOffset);
5991 // Get the app bar layout height. This can't be done in `applyAppSettings()` because the app bar is not yet populated.
5992 int appBarHeight = appBarLayout.getHeight();
5994 // The swipe refresh layout must be manually moved below the app bar layout.
5995 swipeRefreshLayout.setPadding(0, appBarHeight, 0, 0);
5997 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
5998 swipeRefreshLayout.setProgressViewOffset(false, defaultProgressViewStartOffset - 10 + appBarHeight, defaultProgressViewEndOffset + appBarHeight);
6001 // Reset the list of resource requests.
6002 nestedScrollWebView.clearResourceRequests();
6004 // Reset the requests counters.
6005 nestedScrollWebView.resetRequestsCounters();
6007 // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
6008 if (nestedScrollWebView.getNightMode()) {
6009 nestedScrollWebView.setVisibility(View.INVISIBLE);
6011 nestedScrollWebView.setVisibility(View.VISIBLE);
6014 // Hide the keyboard.
6015 inputMethodManager.hideSoftInputFromWindow(nestedScrollWebView.getWindowToken(), 0);
6017 // Get the current page position.
6018 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6020 // Update the URL text bar if the page is currently selected.
6021 if (tabLayout.getSelectedTabPosition() == currentPagePosition) {
6022 // Clear the focus from the URL edit text.
6023 urlEditText.clearFocus();
6025 // Display the formatted URL text.
6026 urlEditText.setText(url);
6028 // Apply text highlighting to `urlTextBox`.
6032 // Reset the list of host IP addresses.
6033 nestedScrollWebView.clearCurrentIpAddresses();
6035 // Get a URI for the current URL.
6036 Uri currentUri = Uri.parse(url);
6038 // Get the IP addresses for the host.
6039 new GetHostIpAddresses(activity, getSupportFragmentManager(), nestedScrollWebView).execute(currentUri.getHost());
6041 // Replace Refresh with Stop if the options menu has been created. (The first WebView typically begins loading before the menu items are instantiated.)
6042 if (optionsMenu != null) {
6043 // Get a handle for the refresh menu item.
6044 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
6047 refreshMenuItem.setTitle(R.string.stop);
6049 // Get the app bar and theme preferences.
6050 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
6052 // If the icon is displayed in the AppBar, set it according to the theme.
6053 if (displayAdditionalAppBarIcons) {
6055 refreshMenuItem.setIcon(R.drawable.close_dark);
6057 refreshMenuItem.setIcon(R.drawable.close_light);
6064 public void onPageFinished(WebView view, String url) {
6065 // Flush any cookies to persistent storage. The cookie manager has become very lazy about flushing cookies in recent versions.
6066 if (nestedScrollWebView.getAcceptFirstPartyCookies() && Build.VERSION.SDK_INT >= 21) {
6067 CookieManager.getInstance().flush();
6070 // Update the Refresh menu item if the options menu has been created.
6071 if (optionsMenu != null) {
6072 // Get a handle for the refresh menu item.
6073 MenuItem refreshMenuItem = optionsMenu.findItem(R.id.refresh);
6075 // Reset the Refresh title.
6076 refreshMenuItem.setTitle(R.string.refresh);
6078 // Get the app bar and theme preferences.
6079 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
6080 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
6082 // If the icon is displayed in the AppBar, reset it according to the theme.
6083 if (displayAdditionalAppBarIcons) {
6085 refreshMenuItem.setIcon(R.drawable.refresh_enabled_dark);
6087 refreshMenuItem.setIcon(R.drawable.refresh_enabled_light);
6092 // Clear the cache and history if Incognito Mode is enabled.
6093 if (incognitoModeEnabled) {
6094 // Clear the cache. `true` includes disk files.
6095 nestedScrollWebView.clearCache(true);
6097 // Clear the back/forward history.
6098 nestedScrollWebView.clearHistory();
6100 // Manually delete cache folders.
6102 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
6103 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
6104 String privateDataDirectoryString = getApplicationInfo().dataDir;
6106 // Delete the main cache directory.
6107 Runtime.getRuntime().exec("rm -rf " + privateDataDirectoryString + "/cache");
6109 // Delete the secondary `Service Worker` cache directory.
6110 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will not escape the string correctly otherwise.
6111 Runtime.getRuntime().exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
6112 } catch (IOException e) {
6113 // Do nothing if an error is thrown.
6117 // Get the current page position.
6118 int currentPagePosition = webViewPagerAdapter.getPositionForId(nestedScrollWebView.getWebViewFragmentId());
6120 // Check the current website information against any pinned domain information if the current IP addresses have been loaded.
6121 if ((nestedScrollWebView.hasPinnedSslCertificate() || nestedScrollWebView.hasPinnedIpAddresses()) && nestedScrollWebView.hasCurrentIpAddresses() &&
6122 !nestedScrollWebView.ignorePinnedDomainInformation()) {
6123 CheckPinnedMismatchHelper.checkPinnedMismatch(getSupportFragmentManager(), nestedScrollWebView);
6126 // 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.
6127 String currentUrl = nestedScrollWebView.getUrl();
6129 // Get the current tab.
6130 TabLayout.Tab tab = tabLayout.getTabAt(currentPagePosition);
6132 // Update the URL text bar if the page is currently selected and the user is not currently typing in the URL edit text.
6133 // Crash records show that, in some crazy way, it is possible for the current URL to be blank at this point.
6134 // Probably some sort of race condition when Privacy Browser is being resumed.
6135 if ((tabLayout.getSelectedTabPosition() == currentPagePosition) && !urlEditText.hasFocus() && (currentUrl != null)) {
6136 // Check to see if the URL is `about:blank`.
6137 if (currentUrl.equals("about:blank")) { // The WebView is blank.
6138 // Display the hint in the URL edit text.
6139 urlEditText.setText("");
6141 // Request focus for the URL text box.
6142 urlEditText.requestFocus();
6144 // Display the keyboard.
6145 inputMethodManager.showSoftInput(urlEditText, 0);
6147 // Apply the domain settings. This clears any settings from the previous domain.
6148 applyDomainSettings(nestedScrollWebView, "", true, false);
6150 // Only populate the title text view if the tab has been fully created.
6152 // Get the custom view from the tab.
6153 View tabView = tab.getCustomView();
6155 // Remove the incorrect warning below that the current tab view might be null.
6156 assert tabView != null;
6158 // Get the title text view from the tab.
6159 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6161 // Set the title as the tab text.
6162 tabTitleTextView.setText(R.string.new_tab);
6164 } else { // The WebView has loaded a webpage.
6165 // Update the URL edit text if it is not currently being edited.
6166 if (!urlEditText.hasFocus()) {
6167 // 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.
6168 String sanitizedUrl = sanitizeUrl(currentUrl);
6170 // Display the final URL. Getting the URL from the WebView instead of using the one provided by `onPageFinished()` makes websites like YouTube function correctly.
6171 urlEditText.setText(sanitizedUrl);
6173 // Apply text highlighting to the URL.
6177 // Only populate the title text view if the tab has been fully created.
6179 // Get the custom view from the tab.
6180 View tabView = tab.getCustomView();
6182 // Remove the incorrect warning below that the current tab view might be null.
6183 assert tabView != null;
6185 // Get the title text view from the tab.
6186 TextView tabTitleTextView = tabView.findViewById(R.id.title_textview);
6188 // Set the title as the tab text. Sometimes `onReceivedTitle()` is not called, especially when navigating history.
6189 tabTitleTextView.setText(nestedScrollWebView.getTitle());
6195 // Handle SSL Certificate errors.
6197 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
6198 // Get the current website SSL certificate.
6199 SslCertificate currentWebsiteSslCertificate = error.getCertificate();
6201 // Extract the individual pieces of information from the current website SSL certificate.
6202 String currentWebsiteIssuedToCName = currentWebsiteSslCertificate.getIssuedTo().getCName();
6203 String currentWebsiteIssuedToOName = currentWebsiteSslCertificate.getIssuedTo().getOName();
6204 String currentWebsiteIssuedToUName = currentWebsiteSslCertificate.getIssuedTo().getUName();
6205 String currentWebsiteIssuedByCName = currentWebsiteSslCertificate.getIssuedBy().getCName();
6206 String currentWebsiteIssuedByOName = currentWebsiteSslCertificate.getIssuedBy().getOName();
6207 String currentWebsiteIssuedByUName = currentWebsiteSslCertificate.getIssuedBy().getUName();
6208 Date currentWebsiteSslStartDate = currentWebsiteSslCertificate.getValidNotBeforeDate();
6209 Date currentWebsiteSslEndDate = currentWebsiteSslCertificate.getValidNotAfterDate();
6211 // Proceed to the website if the current SSL website certificate matches the pinned domain certificate.
6212 if (nestedScrollWebView.hasPinnedSslCertificate()) {
6213 // Get the pinned SSL certificate.
6214 ArrayList<Object> pinnedSslCertificateArrayList = nestedScrollWebView.getPinnedSslCertificate();
6216 // Extract the arrays from the array list.
6217 String[] pinnedSslCertificateStringArray = (String[]) pinnedSslCertificateArrayList.get(0);
6218 Date[] pinnedSslCertificateDateArray = (Date[]) pinnedSslCertificateArrayList.get(1);
6220 // Check if the current SSL certificate matches the pinned certificate.
6221 if (currentWebsiteIssuedToCName.equals(pinnedSslCertificateStringArray[0]) && currentWebsiteIssuedToOName.equals(pinnedSslCertificateStringArray[1]) &&
6222 currentWebsiteIssuedToUName.equals(pinnedSslCertificateStringArray[2]) && currentWebsiteIssuedByCName.equals(pinnedSslCertificateStringArray[3]) &&
6223 currentWebsiteIssuedByOName.equals(pinnedSslCertificateStringArray[4]) && currentWebsiteIssuedByUName.equals(pinnedSslCertificateStringArray[5]) &&
6224 currentWebsiteSslStartDate.equals(pinnedSslCertificateDateArray[0]) && currentWebsiteSslEndDate.equals(pinnedSslCertificateDateArray[1])) {
6226 // An SSL certificate is pinned and matches the current domain certificate. Proceed to the website without displaying an error.
6229 } else { // Either there isn't a pinned SSL certificate or it doesn't match the current website certificate.
6230 // Store the SSL error handler.
6231 nestedScrollWebView.setSslErrorHandler(handler);
6233 // Instantiate an SSL certificate error alert dialog.
6234 DialogFragment sslCertificateErrorDialogFragment = SslCertificateErrorDialog.displayDialog(error, nestedScrollWebView.getWebViewFragmentId());
6236 // Show the SSL certificate error dialog.
6237 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getString(R.string.ssl_certificate_error));
6242 // Check to see if this is the first page.
6243 if (pageNumber == 0) {
6244 // Set this nested scroll WebView as the current WebView.
6245 currentWebView = nestedScrollWebView;
6247 // Apply the app settings from the shared preferences.
6250 // Initialize the URL to load string.
6251 String urlToLoadString;
6253 // Get the intent that started the app.
6254 Intent launchingIntent = getIntent();
6256 // Get the information from the intent.
6257 String launchingIntentAction = launchingIntent.getAction();
6258 Uri launchingIntentUriData = launchingIntent.getData();
6260 // Parse the launching intent URL.
6261 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) { // The intent contains a search string.
6262 // Create an encoded URL string.
6263 String encodedUrlString;
6265 // Sanitize the search input and convert it to a search.
6267 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
6268 } catch (UnsupportedEncodingException exception) {
6269 encodedUrlString = "";
6272 // Store the web search as the URL to load.
6273 urlToLoadString = searchURL + encodedUrlString;
6274 } else if (launchingIntentUriData != null){ // The intent contains a URL.
6276 urlToLoadString = launchingIntentUriData.toString();
6277 } else { // The is no URL in the intent.
6278 // Store the homepage to be loaded.
6279 urlToLoadString = sharedPreferences.getString("homepage", getString(R.string.homepage_default_value));
6282 // Load the website if not waiting for the proxy.
6283 if (waitingForProxy) { // Store the URL to be loaded in the Nested Scroll WebView.
6284 nestedScrollWebView.setWaitingForProxyUrlString(urlToLoadString);
6285 } else { // Load the URL.
6286 loadUrl(nestedScrollWebView, urlToLoadString);
6288 } else { // This is not the first tab.
6289 // Apply the domain settings.
6290 applyDomainSettings(nestedScrollWebView, url, false, false);
6293 nestedScrollWebView.loadUrl(url, customHeaders);
6295 // Set the focus and display the keyboard if the URL is blank.
6296 if (url.equals("")) {
6297 // Request focus for the URL text box.
6298 urlEditText.requestFocus();
6300 // Create a display keyboard handler.
6301 Handler displayKeyboardHandler = new Handler();
6303 // Create a display keyboard runnable.
6304 Runnable displayKeyboardRunnable = () -> {
6305 // Display the keyboard.
6306 inputMethodManager.showSoftInput(urlEditText, 0);
6309 // Display the keyboard after 100 milliseconds, which leaves enough time for the tab to transition.
6310 displayKeyboardHandler.postDelayed(displayKeyboardRunnable, 100);