2 * Copyright © 2015-2021 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.annotation.SuppressLint;
25 import android.app.Activity;
26 import android.app.Dialog;
27 import android.app.DownloadManager;
28 import android.app.SearchManager;
29 import android.content.ActivityNotFoundException;
30 import android.content.BroadcastReceiver;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.SharedPreferences;
37 import android.content.pm.PackageManager;
38 import android.content.res.Configuration;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.Typeface;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.graphics.drawable.Drawable;
45 import android.net.Uri;
46 import android.net.http.SslCertificate;
47 import android.net.http.SslError;
48 import android.os.AsyncTask;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Environment;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.preference.PreferenceManager;
55 import android.print.PrintDocumentAdapter;
56 import android.print.PrintManager;
57 import android.provider.DocumentsContract;
58 import android.text.Editable;
59 import android.text.Spanned;
60 import android.text.TextWatcher;
61 import android.text.style.ForegroundColorSpan;
62 import android.util.Patterns;
63 import android.util.TypedValue;
64 import android.view.ContextMenu;
65 import android.view.GestureDetector;
66 import android.view.KeyEvent;
67 import android.view.Menu;
68 import android.view.MenuItem;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.ViewGroup;
72 import android.view.WindowManager;
73 import android.view.inputmethod.InputMethodManager;
74 import android.webkit.CookieManager;
75 import android.webkit.HttpAuthHandler;
76 import android.webkit.SslErrorHandler;
77 import android.webkit.ValueCallback;
78 import android.webkit.WebBackForwardList;
79 import android.webkit.WebChromeClient;
80 import android.webkit.WebResourceResponse;
81 import android.webkit.WebSettings;
82 import android.webkit.WebStorage;
83 import android.webkit.WebView;
84 import android.webkit.WebViewClient;
85 import android.webkit.WebViewDatabase;
86 import android.widget.ArrayAdapter;
87 import android.widget.CheckBox;
88 import android.widget.CursorAdapter;
89 import android.widget.EditText;
90 import android.widget.FrameLayout;
91 import android.widget.ImageView;
92 import android.widget.LinearLayout;
93 import android.widget.ListView;
94 import android.widget.ProgressBar;
95 import android.widget.RadioButton;
96 import android.widget.RelativeLayout;
97 import android.widget.TextView;
99 import androidx.annotation.NonNull;
100 import androidx.appcompat.app.ActionBar;
101 import androidx.appcompat.app.ActionBarDrawerToggle;
102 import androidx.appcompat.app.AppCompatActivity;
103 import androidx.appcompat.app.AppCompatDelegate;
104 import androidx.appcompat.widget.Toolbar;
105 import androidx.coordinatorlayout.widget.CoordinatorLayout;
106 import androidx.core.content.res.ResourcesCompat;
107 import androidx.core.view.GravityCompat;
108 import androidx.drawerlayout.widget.DrawerLayout;
109 import androidx.fragment.app.DialogFragment;
110 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
111 import androidx.viewpager.widget.ViewPager;
112 import androidx.webkit.WebSettingsCompat;
113 import androidx.webkit.WebViewFeature;
115 import com.google.android.material.appbar.AppBarLayout;
116 import com.google.android.material.floatingactionbutton.FloatingActionButton;
117 import com.google.android.material.navigation.NavigationView;
118 import com.google.android.material.snackbar.Snackbar;
119 import com.google.android.material.tabs.TabLayout;
121 import com.stoutner.privacybrowser.BuildConfig;
122 import com.stoutner.privacybrowser.R;
123 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
124 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
125 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
126 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
127 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
128 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
129 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
130 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
131 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
132 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
133 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
134 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
135 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
136 import com.stoutner.privacybrowser.dialogs.OpenDialog;
137 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
138 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
139 import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
140 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
141 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
142 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
143 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
144 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
145 import com.stoutner.privacybrowser.helpers.AdHelper;
146 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
147 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
148 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
149 import com.stoutner.privacybrowser.helpers.ProxyHelper;
150 import com.stoutner.privacybrowser.views.NestedScrollWebView;
152 import java.io.ByteArrayInputStream;
153 import java.io.ByteArrayOutputStream;
155 import java.io.FileInputStream;
156 import java.io.FileOutputStream;
157 import java.io.IOException;
158 import java.io.InputStream;
159 import java.io.OutputStream;
160 import java.io.UnsupportedEncodingException;
162 import java.net.MalformedURLException;
164 import java.net.URLDecoder;
165 import java.net.URLEncoder;
167 import java.text.NumberFormat;
169 import java.util.ArrayList;
170 import java.util.Calendar;
171 import java.util.Date;
172 import java.util.HashMap;
173 import java.util.HashSet;
174 import java.util.List;
175 import java.util.Map;
176 import java.util.Objects;
177 import java.util.Set;
178 import java.util.concurrent.ExecutorService;
179 import java.util.concurrent.Executors;
181 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
182 EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
183 PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener,
184 WebViewTabFragment.NewTabListener {
186 // The executor service handles background tasks. It is accessed from `ViewSourceActivity`.
187 public static ExecutorService executorService = Executors.newFixedThreadPool(4);
189 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
190 public static String orbotStatus = "unknown";
192 // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`.
193 public static WebViewPagerAdapter webViewPagerAdapter;
195 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
196 public static boolean restartFromBookmarksActivity;
198 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
199 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
200 public static String currentBookmarksFolder;
202 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
203 public final static int UNRECOGNIZED_USER_AGENT = -1;
204 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
205 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
206 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
207 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
208 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
210 // Define the start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
211 private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
212 public final static int BROWSE_OPEN_REQUEST_CODE = 1;
213 public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 2;
215 // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
216 // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
217 // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
218 public static String proxyMode = ProxyHelper.NONE;
220 // Define the saved instance state constants.
221 private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
222 private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
223 private final String SAVED_TAB_POSITION = "saved_tab_position";
224 private final String PROXY_MODE = "proxy_mode";
226 // Define the saved instance state variables.
227 private ArrayList<Bundle> savedStateArrayList;
228 private ArrayList<Bundle> savedNestedScrollWebViewStateArrayList;
229 private int savedTabPosition;
230 private String savedProxyMode;
232 // Define the class variables.
233 @SuppressWarnings("rawtypes")
234 AsyncTask populateBlocklists;
236 // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
237 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
238 private NestedScrollWebView currentWebView;
240 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
241 private final Map<String, String> customHeaders = new HashMap<>();
243 // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
244 private String searchURL;
246 // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
247 private ArrayList<List<String[]>> easyList;
248 private ArrayList<List<String[]>> easyPrivacy;
249 private ArrayList<List<String[]>> fanboysAnnoyanceList;
250 private ArrayList<List<String[]>> fanboysSocialList;
251 private ArrayList<List<String[]>> ultraList;
252 private ArrayList<List<String[]>> ultraPrivacy;
254 // Declare the class variables
255 private BroadcastReceiver orbotStatusBroadcastReceiver;
256 private String webViewDefaultUserAgent;
257 private boolean incognitoModeEnabled;
258 private boolean fullScreenBrowsingModeEnabled;
259 private boolean inFullScreenBrowsingMode;
260 private boolean downloadWithExternalApp;
261 private boolean hideAppBar;
262 private boolean scrollAppBar;
263 private boolean bottomAppBar;
264 private boolean loadingNewIntent;
265 private boolean reapplyDomainSettingsOnRestart;
266 private boolean reapplyAppSettingsOnRestart;
267 private boolean displayingFullScreenVideo;
268 private boolean waitingForProxy;
270 // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
271 private ActionBarDrawerToggle actionBarDrawerToggle;
273 // The color spans are used in `onCreate()` and `highlightUrlText()`.
274 private ForegroundColorSpan redColorSpan;
275 private ForegroundColorSpan initialGrayColorSpan;
276 private ForegroundColorSpan finalGrayColorSpan;
278 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
279 // and `loadBookmarksFolder()`.
280 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
282 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
283 private Cursor bookmarksCursor;
285 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
286 private CursorAdapter bookmarksCursorAdapter;
288 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
289 private String oldFolderNameString;
291 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
292 private ValueCallback<Uri[]> fileChooserCallback;
294 // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
295 private int appBarHeight;
296 private int defaultProgressViewStartOffset;
297 private int defaultProgressViewEndOffset;
299 // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
300 private boolean sanitizeGoogleAnalytics;
301 private boolean sanitizeFacebookClickIds;
302 private boolean sanitizeTwitterAmpRedirects;
304 // Define the class variables.
305 private long lastScrollUpdate = 0;
307 // Declare the class views.
308 private FrameLayout rootFrameLayout;
309 private DrawerLayout drawerLayout;
310 private RelativeLayout mainContentRelativeLayout;
311 private AppBarLayout appBarLayout;
312 private Toolbar toolbar;
313 private RelativeLayout urlRelativeLayout;
314 private EditText urlEditText;
315 private ActionBar actionBar;
316 private LinearLayout findOnPageLinearLayout;
317 private LinearLayout tabsLinearLayout;
318 private TabLayout tabLayout;
319 private SwipeRefreshLayout swipeRefreshLayout;
320 private ViewPager webViewPager;
321 private FrameLayout fullScreenVideoFrameLayout;
323 // Declare the class menus.
324 private Menu optionsMenu;
326 // Declare the class menu items.
327 private MenuItem navigationBackMenuItem;
328 private MenuItem navigationForwardMenuItem;
329 private MenuItem navigationHistoryMenuItem;
330 private MenuItem navigationRequestsMenuItem;
331 private MenuItem optionsPrivacyMenuItem;
332 private MenuItem optionsRefreshMenuItem;
333 private MenuItem optionsCookiesMenuItem;
334 private MenuItem optionsDomStorageMenuItem;
335 private MenuItem optionsSaveFormDataMenuItem;
336 private MenuItem optionsClearDataMenuItem;
337 private MenuItem optionsClearCookiesMenuItem;
338 private MenuItem optionsClearDomStorageMenuItem;
339 private MenuItem optionsClearFormDataMenuItem;
340 private MenuItem optionsBlocklistsMenuItem;
341 private MenuItem optionsEasyListMenuItem;
342 private MenuItem optionsEasyPrivacyMenuItem;
343 private MenuItem optionsFanboysAnnoyanceListMenuItem;
344 private MenuItem optionsFanboysSocialBlockingListMenuItem;
345 private MenuItem optionsUltraListMenuItem;
346 private MenuItem optionsUltraPrivacyMenuItem;
347 private MenuItem optionsBlockAllThirdPartyRequestsMenuItem;
348 private MenuItem optionsProxyMenuItem;
349 private MenuItem optionsProxyNoneMenuItem;
350 private MenuItem optionsProxyTorMenuItem;
351 private MenuItem optionsProxyI2pMenuItem;
352 private MenuItem optionsProxyCustomMenuItem;
353 private MenuItem optionsUserAgentMenuItem;
354 private MenuItem optionsUserAgentPrivacyBrowserMenuItem;
355 private MenuItem optionsUserAgentWebViewDefaultMenuItem;
356 private MenuItem optionsUserAgentFirefoxOnAndroidMenuItem;
357 private MenuItem optionsUserAgentChromeOnAndroidMenuItem;
358 private MenuItem optionsUserAgentSafariOnIosMenuItem;
359 private MenuItem optionsUserAgentFirefoxOnLinuxMenuItem;
360 private MenuItem optionsUserAgentChromiumOnLinuxMenuItem;
361 private MenuItem optionsUserAgentFirefoxOnWindowsMenuItem;
362 private MenuItem optionsUserAgentChromeOnWindowsMenuItem;
363 private MenuItem optionsUserAgentEdgeOnWindowsMenuItem;
364 private MenuItem optionsUserAgentInternetExplorerOnWindowsMenuItem;
365 private MenuItem optionsUserAgentSafariOnMacosMenuItem;
366 private MenuItem optionsUserAgentCustomMenuItem;
367 private MenuItem optionsSwipeToRefreshMenuItem;
368 private MenuItem optionsWideViewportMenuItem;
369 private MenuItem optionsDisplayImagesMenuItem;
370 private MenuItem optionsDarkWebViewMenuItem;
371 private MenuItem optionsFontSizeMenuItem;
372 private MenuItem optionsAddOrEditDomainMenuItem;
375 // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
376 @SuppressLint("ClickableViewAccessibility")
377 protected void onCreate(Bundle savedInstanceState) {
378 // Run the default commands.
379 super.onCreate(savedInstanceState);
381 // Check to see if the activity has been restarted.
382 if (savedInstanceState != null) {
383 // Store the saved instance state variables.
384 savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
385 savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
386 savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
387 savedProxyMode = savedInstanceState.getString(PROXY_MODE);
390 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
391 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
393 // Get a handle for the shared preferences.
394 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
396 // Get the preferences.
397 String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
398 boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
399 bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
401 // Get the theme entry values string array.
402 String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
404 // Set the app theme according to the preference. A switch statement cannot be used because the theme entry values string array is not a compile time constant.
405 if (appTheme.equals(appThemeEntryValuesStringArray[1])) { // The light theme is selected.
406 // Apply the light theme.
407 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
408 } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) { // The dark theme is selected.
409 // Apply the dark theme.
410 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
411 } else { // The system default theme is selected.
412 if (Build.VERSION.SDK_INT >= 28) { // The system default theme is supported.
413 // Follow the system default theme.
414 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
415 } else { // The system default theme is not supported.
416 // Follow the battery saver mode.
417 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
421 // Disable screenshots if not allowed.
422 if (!allowScreenshots) {
423 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
426 // 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.
427 if (Build.VERSION.SDK_INT >= 21) {
428 WebView.enableSlowWholeDocumentDraw();
432 setTheme(R.style.PrivacyBrowser);
434 // Set the content view.
436 setContentView(R.layout.main_framelayout_bottom_appbar);
438 setContentView(R.layout.main_framelayout_top_appbar);
441 // Get handles for the views.
442 rootFrameLayout = findViewById(R.id.root_framelayout);
443 drawerLayout = findViewById(R.id.drawerlayout);
444 mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
445 appBarLayout = findViewById(R.id.appbar_layout);
446 toolbar = findViewById(R.id.toolbar);
447 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
448 tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
449 tabLayout = findViewById(R.id.tablayout);
450 swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
451 webViewPager = findViewById(R.id.webviewpager);
452 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
454 // Get a handle for the navigation view.
455 NavigationView navigationView = findViewById(R.id.navigationview);
457 // Get a handle for the navigation menu.
458 Menu navigationMenu = navigationView.getMenu();
460 // Get handles for the navigation menu items.
461 navigationBackMenuItem = navigationMenu.findItem(R.id.back);
462 navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
463 navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
464 navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
466 // Listen for touches on the navigation menu.
467 navigationView.setNavigationItemSelectedListener(this);
469 // Get a handle for the app compat delegate.
470 AppCompatDelegate appCompatDelegate = getDelegate();
472 // Set the support action bar.
473 appCompatDelegate.setSupportActionBar(toolbar);
475 // Get a handle for the action bar.
476 actionBar = appCompatDelegate.getSupportActionBar();
478 // Remove the incorrect lint warning below that the action bar might be null.
479 assert actionBar != null;
481 // Add the custom layout, which shows the URL text bar.
482 actionBar.setCustomView(R.layout.url_app_bar);
483 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
485 // Get handles for the views in the URL app bar.
486 urlRelativeLayout = findViewById(R.id.url_relativelayout);
487 urlEditText = findViewById(R.id.url_edittext);
489 // Create the hamburger icon at the start of the AppBar.
490 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
492 // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded.
493 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
495 // Initialize the web view pager adapter.
496 webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
498 // Set the pager adapter on the web view pager.
499 webViewPager.setAdapter(webViewPagerAdapter);
501 // Store up to 100 tabs in memory.
502 webViewPager.setOffscreenPageLimit(100);
504 // Initialize the app.
507 // Apply the app settings from the shared preferences.
510 // Populate the blocklists.
511 populateBlocklists = new PopulateBlocklists(this, this).execute();
515 protected void onNewIntent(Intent intent) {
516 // Run the default commands.
517 super.onNewIntent(intent);
519 // Replace the intent that started the app with this one.
522 // Check to see if the app is being restarted from a saved state.
523 if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is not being restarted from a saved state.
524 // Get the information from the intent.
525 String intentAction = intent.getAction();
526 Uri intentUriData = intent.getData();
527 String intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
529 // Determine if this is a web search.
530 boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
532 // 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.
533 if (intentUriData != null || intentStringExtra != null || isWebSearch) {
534 // Exit the full screen video if it is displayed.
535 if (displayingFullScreenVideo) {
536 // Exit full screen video mode.
537 exitFullScreenVideo();
539 // Reload the current WebView. Otherwise, it can display entirely black.
540 currentWebView.reload();
543 // Get the shared preferences.
544 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
546 // Create a URL string.
549 // If the intent action is a web search, perform the search.
550 if (isWebSearch) { // The intent is a web search.
551 // Create an encoded URL string.
552 String encodedUrlString;
554 // Sanitize the search input and convert it to a search.
556 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
557 } catch (UnsupportedEncodingException exception) {
558 encodedUrlString = "";
561 // Add the base search URL.
562 url = searchURL + encodedUrlString;
563 } else if (intentUriData != null) { // The intent contains a URL formatted as a URI.
564 // Set the intent data as the URL.
565 url = intentUriData.toString();
566 } else { // The intent contains a string, which might be a URL.
567 // Set the intent string as the URL.
568 url = intentStringExtra;
571 // Add a new tab if specified in the preferences.
572 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
573 // Set the loading new intent flag.
574 loadingNewIntent = true;
577 addNewTab(url, true);
578 } else { // Load the URL in the current tab.
580 loadUrl(currentWebView, url);
583 // Close the navigation drawer if it is open.
584 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
585 drawerLayout.closeDrawer(GravityCompat.START);
588 // Close the bookmarks drawer if it is open.
589 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
590 drawerLayout.closeDrawer(GravityCompat.END);
597 public void onRestart() {
598 // Run the default commands.
601 // Apply the app settings if returning from the Settings activity.
602 if (reapplyAppSettingsOnRestart) {
603 // Reset the reapply app settings on restart tracker.
604 reapplyAppSettingsOnRestart = false;
606 // Apply the app settings.
610 // Apply the domain settings if returning from the settings or domains activity.
611 if (reapplyDomainSettingsOnRestart) {
612 // Reset the reapply domain settings on restart tracker.
613 reapplyDomainSettingsOnRestart = false;
615 // Reapply the domain settings for each tab.
616 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
617 // Get the WebView tab fragment.
618 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
620 // Get the fragment view.
621 View fragmentView = webViewTabFragment.getView();
623 // Only reload the WebViews if they exist.
624 if (fragmentView != null) {
625 // Get the nested scroll WebView from the tab fragment.
626 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
628 // Reset the current domain name so the domain settings will be reapplied.
629 nestedScrollWebView.resetCurrentDomainName();
631 // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
632 if (nestedScrollWebView.getUrl() != null) {
633 applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true, false);
639 // Update the bookmarks drawer if returning from the Bookmarks activity.
640 if (restartFromBookmarksActivity) {
641 // Close the bookmarks drawer.
642 drawerLayout.closeDrawer(GravityCompat.END);
644 // Reload the bookmarks drawer.
645 loadBookmarksFolder();
647 // Reset `restartFromBookmarksActivity`.
648 restartFromBookmarksActivity = false;
651 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
652 updatePrivacyIcons(true);
655 // `onStart()` runs after `onCreate()` or `onRestart()`. This is used instead of `onResume()` so the commands aren't called every time the screen is partially hidden.
657 public void onStart() {
658 // Run the default commands.
661 // Resume any WebViews.
662 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
663 // Get the WebView tab fragment.
664 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
666 // Get the fragment view.
667 View fragmentView = webViewTabFragment.getView();
669 // Only resume the WebViews if they exist (they won't when the app is first created).
670 if (fragmentView != null) {
671 // Get the nested scroll WebView from the tab fragment.
672 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
674 // Resume the nested scroll WebView.
675 nestedScrollWebView.onResume();
679 // Resume the nested scroll WebView JavaScript timers. This is a global command that resumes JavaScript timers on all WebViews.
680 if (currentWebView != null) {
681 currentWebView.resumeTimers();
684 // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog.
685 if (!proxyMode.equals(ProxyHelper.NONE)) {
689 // Reapply any system UI flags and the ad in the free flavor.
690 if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode.
691 /* Hide the system bars.
692 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
693 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
694 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
695 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
697 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
698 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
699 } else if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode.
700 // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
701 View adView = findViewById(R.id.adview);
704 AdHelper.resumeAd(adView);
708 // `onStop()` runs after `onPause()`. It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
710 public void onStop() {
711 // Run the default commands.
714 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
715 // Get the WebView tab fragment.
716 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
718 // Get the fragment view.
719 View fragmentView = webViewTabFragment.getView();
721 // Only pause the WebViews if they exist (they won't when the app is first created).
722 if (fragmentView != null) {
723 // Get the nested scroll WebView from the tab fragment.
724 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
726 // Pause the nested scroll WebView.
727 nestedScrollWebView.onPause();
731 // Pause the WebView JavaScript timers. This is a global command that pauses JavaScript on all WebViews.
732 if (currentWebView != null) {
733 currentWebView.pauseTimers();
736 // Pause the ad or it will continue to consume resources in the background on the free flavor.
737 if (BuildConfig.FLAVOR.contentEquals("free")) {
738 // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
739 View adView = findViewById(R.id.adview);
742 AdHelper.pauseAd(adView);
747 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
748 // Run the default commands.
749 super.onSaveInstanceState(savedInstanceState);
751 // Create the saved state array lists.
752 ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
753 ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
755 // Get the URLs from each tab.
756 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
757 // Get the WebView tab fragment.
758 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
760 // Get the fragment view.
761 View fragmentView = webViewTabFragment.getView();
763 if (fragmentView != null) {
764 // Get the nested scroll WebView from the tab fragment.
765 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
767 // Create saved state bundle.
768 Bundle savedStateBundle = new Bundle();
770 // Get the current states.
771 nestedScrollWebView.saveState(savedStateBundle);
772 Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
774 // Store the saved states in the array lists.
775 savedStateArrayList.add(savedStateBundle);
776 savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
780 // Get the current tab position.
781 int currentTabPosition = tabLayout.getSelectedTabPosition();
783 // Store the saved states in the bundle.
784 savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
785 savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
786 savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
787 savedInstanceState.putString(PROXY_MODE, proxyMode);
791 public void onDestroy() {
792 // Unregister the orbot status broadcast receiver if it exists.
793 if (orbotStatusBroadcastReceiver != null) {
794 this.unregisterReceiver(orbotStatusBroadcastReceiver);
797 // Close the bookmarks cursor if it exists.
798 if (bookmarksCursor != null) {
799 bookmarksCursor.close();
802 // Close the bookmarks database if it exists.
803 if (bookmarksDatabaseHelper != null) {
804 bookmarksDatabaseHelper.close();
807 // Stop populating the blocklists if the AsyncTask is running in the background.
808 if (populateBlocklists != null) {
809 populateBlocklists.cancel(true);
812 // Run the default commands.
817 public boolean onCreateOptionsMenu(Menu menu) {
818 // Inflate the menu. This adds items to the action bar if it is present.
819 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
821 // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
824 // Get handles for the class menu items.
825 optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
826 optionsRefreshMenuItem = menu.findItem(R.id.refresh);
827 optionsCookiesMenuItem = menu.findItem(R.id.cookies);
828 optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage);
829 optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data); // Form data can be removed once the minimum API >= 26.
830 optionsClearDataMenuItem = menu.findItem(R.id.clear_data);
831 optionsClearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
832 optionsClearDomStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
833 optionsClearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
834 optionsBlocklistsMenuItem = menu.findItem(R.id.blocklists);
835 optionsEasyListMenuItem = menu.findItem(R.id.easylist);
836 optionsEasyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
837 optionsFanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
838 optionsFanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
839 optionsUltraListMenuItem = menu.findItem(R.id.ultralist);
840 optionsUltraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
841 optionsBlockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
842 optionsProxyMenuItem = menu.findItem(R.id.proxy);
843 optionsProxyNoneMenuItem = menu.findItem(R.id.proxy_none);
844 optionsProxyTorMenuItem = menu.findItem(R.id.proxy_tor);
845 optionsProxyI2pMenuItem = menu.findItem(R.id.proxy_i2p);
846 optionsProxyCustomMenuItem = menu.findItem(R.id.proxy_custom);
847 optionsUserAgentMenuItem = menu.findItem(R.id.user_agent);
848 optionsUserAgentPrivacyBrowserMenuItem = menu.findItem(R.id.user_agent_privacy_browser);
849 optionsUserAgentWebViewDefaultMenuItem = menu.findItem(R.id.user_agent_webview_default);
850 optionsUserAgentFirefoxOnAndroidMenuItem = menu.findItem(R.id.user_agent_firefox_on_android);
851 optionsUserAgentChromeOnAndroidMenuItem = menu.findItem(R.id.user_agent_chrome_on_android);
852 optionsUserAgentSafariOnIosMenuItem = menu.findItem(R.id.user_agent_safari_on_ios);
853 optionsUserAgentFirefoxOnLinuxMenuItem = menu.findItem(R.id.user_agent_firefox_on_linux);
854 optionsUserAgentChromiumOnLinuxMenuItem = menu.findItem(R.id.user_agent_chromium_on_linux);
855 optionsUserAgentFirefoxOnWindowsMenuItem = menu.findItem(R.id.user_agent_firefox_on_windows);
856 optionsUserAgentChromeOnWindowsMenuItem = menu.findItem(R.id.user_agent_chrome_on_windows);
857 optionsUserAgentEdgeOnWindowsMenuItem = menu.findItem(R.id.user_agent_edge_on_windows);
858 optionsUserAgentInternetExplorerOnWindowsMenuItem = menu.findItem(R.id.user_agent_internet_explorer_on_windows);
859 optionsUserAgentSafariOnMacosMenuItem = menu.findItem(R.id.user_agent_safari_on_macos);
860 optionsUserAgentCustomMenuItem = menu.findItem(R.id.user_agent_custom);
861 optionsSwipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
862 optionsWideViewportMenuItem = menu.findItem(R.id.wide_viewport);
863 optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images);
864 optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview);
865 optionsFontSizeMenuItem = menu.findItem(R.id.font_size);
866 optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain);
868 // Get handles for the method menu items.
869 MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks);
870 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
872 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
873 updatePrivacyIcons(false);
875 // Only display the form data menu items if the API < 26.
876 optionsSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
877 optionsClearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
879 // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
880 optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
882 // Only display the dark WebView menu item if API >= 21.
883 optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
885 // Only show Ad Consent if this is the free flavor.
886 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
888 // Get the shared preferences.
889 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
891 // Get the dark theme and app bar preferences.
892 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
894 // 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.
895 if (displayAdditionalAppBarIcons) {
896 optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
897 bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
898 optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
899 } else { //Do not display the additional icons.
900 optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
901 bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
902 optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
905 // Replace Refresh with Stop if a URL is already loading.
906 if (currentWebView != null && currentWebView.getProgress() != 100) {
908 optionsRefreshMenuItem.setTitle(R.string.stop);
910 // Set the icon if it is displayed in the app bar.
911 if (displayAdditionalAppBarIcons) {
912 // Get the current theme status.
913 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
915 // Set the icon according to the current theme status.
916 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
917 optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
919 optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
929 public boolean onPrepareOptionsMenu(Menu menu) {
930 // Get a handle for the cookie manager.
931 CookieManager cookieManager = CookieManager.getInstance();
933 // Initialize the current user agent string and the font size.
934 String currentUserAgent = getString(R.string.user_agent_privacy_browser);
937 // 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.
938 if (currentWebView != null) {
939 // Set the add or edit domain text.
940 if (currentWebView.getDomainSettingsApplied()) {
941 optionsAddOrEditDomainMenuItem.setTitle(R.string.edit_domain_settings);
943 optionsAddOrEditDomainMenuItem.setTitle(R.string.add_domain_settings);
946 // Get the current user agent from the WebView.
947 currentUserAgent = currentWebView.getSettings().getUserAgentString();
949 // Get the current font size from the
950 fontSize = currentWebView.getSettings().getTextZoom();
952 // Set the status of the menu item checkboxes.
953 optionsDomStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
954 optionsSaveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
955 optionsEasyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
956 optionsEasyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
957 optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
958 optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
959 optionsUltraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
960 optionsUltraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
961 optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
962 optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
963 optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
964 optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
966 // Initialize the display names for the blocklists with the number of blocked requests.
967 optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
968 optionsEasyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
969 optionsEasyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
970 optionsFanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
971 optionsFanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
972 optionsUltraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
973 optionsUltraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
974 optionsBlockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
976 // Enable DOM Storage if JavaScript is enabled.
977 optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
979 // Set the checkbox status for dark WebView if the WebView supports it.
980 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
981 optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
985 // Set the cookies menu item checked status.
986 optionsCookiesMenuItem.setChecked(cookieManager.acceptCookie());
988 // Enable Clear Cookies if there are any.
989 optionsClearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
991 // 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`.
992 String privateDataDirectoryString = getApplicationInfo().dataDir;
994 // Get a count of the number of files in the Local Storage directory.
995 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
996 int localStorageDirectoryNumberOfFiles = 0;
997 if (localStorageDirectory.exists()) {
998 // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
999 localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
1002 // Get a count of the number of files in the IndexedDB directory.
1003 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
1004 int indexedDBDirectoryNumberOfFiles = 0;
1005 if (indexedDBDirectory.exists()) {
1006 // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
1007 indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
1010 // Enable Clear DOM Storage if there is any.
1011 optionsClearDomStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
1013 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
1014 if (Build.VERSION.SDK_INT < 26) {
1015 // Get the WebView database.
1016 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1018 // Enable the clear form data menu item if there is anything to clear.
1019 optionsClearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
1022 // Enable Clear Data if any of the submenu items are enabled.
1023 optionsClearDataMenuItem.setEnabled(optionsClearCookiesMenuItem.isEnabled() || optionsClearDomStorageMenuItem.isEnabled() || optionsClearFormDataMenuItem.isEnabled());
1025 // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1026 optionsFanboysSocialBlockingListMenuItem.setEnabled(!optionsFanboysAnnoyanceListMenuItem.isChecked());
1028 // Set the proxy title and check the applied proxy.
1029 switch (proxyMode) {
1030 case ProxyHelper.NONE:
1031 // Set the proxy title.
1032 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
1034 // Check the proxy None radio button.
1035 optionsProxyNoneMenuItem.setChecked(true);
1038 case ProxyHelper.TOR:
1039 // Set the proxy title.
1040 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
1042 // Check the proxy Tor radio button.
1043 optionsProxyTorMenuItem.setChecked(true);
1046 case ProxyHelper.I2P:
1047 // Set the proxy title.
1048 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
1050 // Check the proxy I2P radio button.
1051 optionsProxyI2pMenuItem.setChecked(true);
1054 case ProxyHelper.CUSTOM:
1055 // Set the proxy title.
1056 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
1058 // Check the proxy Custom radio button.
1059 optionsProxyCustomMenuItem.setChecked(true);
1063 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
1064 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
1065 // Update the user agent menu item title.
1066 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
1068 // Select the Privacy Browser radio box.
1069 optionsUserAgentPrivacyBrowserMenuItem.setChecked(true);
1070 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
1071 // Update the user agent menu item title.
1072 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
1074 // Select the WebView Default radio box.
1075 optionsUserAgentWebViewDefaultMenuItem.setChecked(true);
1076 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
1077 // Update the user agent menu item title.
1078 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
1080 // Select the Firefox on Android radio box.
1081 optionsUserAgentFirefoxOnAndroidMenuItem.setChecked(true);
1082 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
1083 // Update the user agent menu item title.
1084 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
1086 // Select the Chrome on Android radio box.
1087 optionsUserAgentChromeOnAndroidMenuItem.setChecked(true);
1088 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
1089 // Update the user agent menu item title.
1090 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
1092 // Select the Safari on iOS radio box.
1093 optionsUserAgentSafariOnIosMenuItem.setChecked(true);
1094 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
1095 // Update the user agent menu item title.
1096 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
1098 // Select the Firefox on Linux radio box.
1099 optionsUserAgentFirefoxOnLinuxMenuItem.setChecked(true);
1100 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
1101 // Update the user agent menu item title.
1102 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
1104 // Select the Chromium on Linux radio box.
1105 optionsUserAgentChromiumOnLinuxMenuItem.setChecked(true);
1106 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
1107 // Update the user agent menu item title.
1108 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
1110 // Select the Firefox on Windows radio box.
1111 optionsUserAgentFirefoxOnWindowsMenuItem.setChecked(true);
1112 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
1113 // Update the user agent menu item title.
1114 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
1116 // Select the Chrome on Windows radio box.
1117 optionsUserAgentChromeOnWindowsMenuItem.setChecked(true);
1118 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
1119 // Update the user agent menu item title.
1120 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
1122 // Select the Edge on Windows radio box.
1123 optionsUserAgentEdgeOnWindowsMenuItem.setChecked(true);
1124 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
1125 // Update the user agent menu item title.
1126 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
1128 // Select the Internet on Windows radio box.
1129 optionsUserAgentInternetExplorerOnWindowsMenuItem.setChecked(true);
1130 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
1131 // Update the user agent menu item title.
1132 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
1134 // Select the Safari on macOS radio box.
1135 optionsUserAgentSafariOnMacosMenuItem.setChecked(true);
1136 } else { // Custom user agent.
1137 // Update the user agent menu item title.
1138 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
1140 // Select the Custom radio box.
1141 optionsUserAgentCustomMenuItem.setChecked(true);
1144 // Set the font size title.
1145 optionsFontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
1147 // Run all the other default commands.
1148 super.onPrepareOptionsMenu(menu);
1150 // Display the menu.
1155 public boolean onOptionsItemSelected(MenuItem menuItem) {
1156 // Get a handle for the shared preferences.
1157 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1159 // Get a handle for the cookie manager.
1160 CookieManager cookieManager = CookieManager.getInstance();
1162 // Get the selected menu item ID.
1163 int menuItemId = menuItem.getItemId();
1165 // Run the commands that correlate to the selected menu item.
1166 if (menuItemId == R.id.javascript) { // JavaScript.
1167 // Toggle the JavaScript status.
1168 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1170 // Update the privacy icon.
1171 updatePrivacyIcons(true);
1173 // Display a `Snackbar`.
1174 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled.
1175 Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1176 } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled.
1177 Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1178 } else { // Privacy mode.
1179 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1182 // Reload the current WebView.
1183 currentWebView.reload();
1185 // Consume the event.
1187 } else if (menuItemId == R.id.refresh) { // Refresh.
1188 // Run the command that correlates to the current status of the menu item.
1189 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
1190 // Reload the current WebView.
1191 currentWebView.reload();
1192 } else { // The stop button was pushed.
1193 // Stop the loading of the WebView.
1194 currentWebView.stopLoading();
1197 // Consume the event.
1199 } else if (menuItemId == R.id.bookmarks) { // Bookmarks.
1200 // Open the bookmarks drawer.
1201 drawerLayout.openDrawer(GravityCompat.END);
1203 // Consume the event.
1205 } else if (menuItemId == R.id.cookies) { // Cookies.
1206 // Switch the first-party cookie status.
1207 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1209 // Store the cookie status.
1210 currentWebView.setAcceptCookies(cookieManager.acceptCookie());
1212 // Update the menu checkbox.
1213 menuItem.setChecked(cookieManager.acceptCookie());
1215 // Update the privacy icon.
1216 updatePrivacyIcons(true);
1218 // Display a snackbar.
1219 if (cookieManager.acceptCookie()) { // Cookies are enabled.
1220 Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show();
1221 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
1222 Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show();
1223 } else { // Privacy mode.
1224 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1227 // Reload the current WebView.
1228 currentWebView.reload();
1230 // Consume the event.
1232 } else if (menuItemId == R.id.dom_storage) { // DOM storage.
1233 // Toggle the status of domStorageEnabled.
1234 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1236 // Update the menu checkbox.
1237 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1239 // Update the privacy icon.
1240 updatePrivacyIcons(true);
1242 // Display a snackbar.
1243 if (currentWebView.getSettings().getDomStorageEnabled()) {
1244 Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1246 Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1249 // Reload the current WebView.
1250 currentWebView.reload();
1252 // Consume the event.
1254 } else if (menuItemId == R.id.save_form_data) { // Form data. This can be removed once the minimum API >= 26.
1255 // Switch the status of saveFormDataEnabled.
1256 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1258 // Update the menu checkbox.
1259 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1261 // Display a snackbar.
1262 if (currentWebView.getSettings().getSaveFormData()) {
1263 Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1265 Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1268 // Update the privacy icon.
1269 updatePrivacyIcons(true);
1271 // Reload the current WebView.
1272 currentWebView.reload();
1274 // Consume the event.
1276 } else if (menuItemId == R.id.clear_cookies) { // Clear cookies.
1277 // Create a snackbar.
1278 Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1279 .setAction(R.string.undo, v -> {
1280 // Do nothing because everything will be handled by `onDismissed()` below.
1282 .addCallback(new Snackbar.Callback() {
1284 public void onDismissed(Snackbar snackbar, int event) {
1285 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1286 // Delete the cookies, which command varies by SDK.
1287 if (Build.VERSION.SDK_INT < 21) {
1288 cookieManager.removeAllCookie();
1290 cookieManager.removeAllCookies(null);
1297 // Consume the event.
1299 } else if (menuItemId == R.id.clear_dom_storage) { // Clear DOM storage.
1300 // Create a snackbar.
1301 Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1302 .setAction(R.string.undo, v -> {
1303 // Do nothing because everything will be handled by `onDismissed()` below.
1305 .addCallback(new Snackbar.Callback() {
1307 public void onDismissed(Snackbar snackbar, int event) {
1308 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1309 // Delete the DOM Storage.
1310 WebStorage webStorage = WebStorage.getInstance();
1311 webStorage.deleteAllData();
1313 // Initialize a handler to manually delete the DOM storage files and directories.
1314 Handler deleteDomStorageHandler = new Handler();
1316 // Setup a runnable to manually delete the DOM storage files and directories.
1317 Runnable deleteDomStorageRunnable = () -> {
1319 // Get a handle for the runtime.
1320 Runtime runtime = Runtime.getRuntime();
1322 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1323 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1324 String privateDataDirectoryString = getApplicationInfo().dataDir;
1326 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1327 Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1329 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1330 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1331 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1332 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1333 Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1335 // Wait for the processes to finish.
1336 deleteLocalStorageProcess.waitFor();
1337 deleteIndexProcess.waitFor();
1338 deleteQuotaManagerProcess.waitFor();
1339 deleteQuotaManagerJournalProcess.waitFor();
1340 deleteDatabasesProcess.waitFor();
1341 } catch (Exception exception) {
1342 // Do nothing if an error is thrown.
1346 // Manually delete the DOM storage files after 200 milliseconds.
1347 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1353 // Consume the event.
1355 } else if (menuItemId == R.id.clear_form_data) { // Clear form data. This can be remove once the minimum API >= 26.
1356 // Create a snackbar.
1357 Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1358 .setAction(R.string.undo, v -> {
1359 // Do nothing because everything will be handled by `onDismissed()` below.
1361 .addCallback(new Snackbar.Callback() {
1363 public void onDismissed(Snackbar snackbar, int event) {
1364 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1365 // Get a handle for the webView database.
1366 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1368 // Delete the form data.
1369 webViewDatabase.clearFormData();
1375 // Consume the event.
1377 } else if (menuItemId == R.id.easylist) { // EasyList.
1378 // Toggle the EasyList status.
1379 currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1381 // Update the menu checkbox.
1382 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1384 // Reload the current WebView.
1385 currentWebView.reload();
1387 // Consume the event.
1389 } else if (menuItemId == R.id.easyprivacy) { // EasyPrivacy.
1390 // Toggle the EasyPrivacy status.
1391 currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1393 // Update the menu checkbox.
1394 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1396 // Reload the current WebView.
1397 currentWebView.reload();
1399 // Consume the event.
1401 } else if (menuItemId == R.id.fanboys_annoyance_list) { // Fanboy's Annoyance List.
1402 // Toggle Fanboy's Annoyance List status.
1403 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1405 // Update the menu checkbox.
1406 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1408 // Update the status of Fanboy's Social Blocking List.
1409 optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1411 // Reload the current WebView.
1412 currentWebView.reload();
1414 // Consume the event.
1416 } else if (menuItemId == R.id.fanboys_social_blocking_list) { // Fanboy's Social Blocking List.
1417 // Toggle Fanboy's Social Blocking List status.
1418 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1420 // Update the menu checkbox.
1421 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1423 // Reload the current WebView.
1424 currentWebView.reload();
1426 // Consume the event.
1428 } else if (menuItemId == R.id.ultralist) { // UltraList.
1429 // Toggle the UltraList status.
1430 currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1432 // Update the menu checkbox.
1433 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1435 // Reload the current WebView.
1436 currentWebView.reload();
1438 // Consume the event.
1440 } else if (menuItemId == R.id.ultraprivacy) { // UltraPrivacy.
1441 // Toggle the UltraPrivacy status.
1442 currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1444 // Update the menu checkbox.
1445 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1447 // Reload the current WebView.
1448 currentWebView.reload();
1450 // Consume the event.
1452 } else if (menuItemId == R.id.block_all_third_party_requests) { // Block all third-party requests.
1453 //Toggle the third-party requests blocker status.
1454 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1456 // Update the menu checkbox.
1457 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1459 // Reload the current WebView.
1460 currentWebView.reload();
1462 // Consume the event.
1464 } else if (menuItemId == R.id.proxy_none) { // Proxy - None.
1465 // Update the proxy mode.
1466 proxyMode = ProxyHelper.NONE;
1468 // Apply the proxy mode.
1471 // Consume the event.
1473 } else if (menuItemId == R.id.proxy_tor) { // Proxy - Tor.
1474 // Update the proxy mode.
1475 proxyMode = ProxyHelper.TOR;
1477 // Apply the proxy mode.
1480 // Consume the event.
1482 } else if (menuItemId == R.id.proxy_i2p) { // Proxy - I2P.
1483 // Update the proxy mode.
1484 proxyMode = ProxyHelper.I2P;
1486 // Apply the proxy mode.
1489 // Consume the event.
1491 } else if (menuItemId == R.id.proxy_custom) { // Proxy - Custom.
1492 // Update the proxy mode.
1493 proxyMode = ProxyHelper.CUSTOM;
1495 // Apply the proxy mode.
1498 // Consume the event.
1500 } else if (menuItemId == R.id.user_agent_privacy_browser) { // User Agent - Privacy Browser.
1501 // Update the user agent.
1502 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1504 // Reload the current WebView.
1505 currentWebView.reload();
1507 // Consume the event.
1509 } else if (menuItemId == R.id.user_agent_webview_default) { // User Agent - WebView Default.
1510 // Update the user agent.
1511 currentWebView.getSettings().setUserAgentString("");
1513 // Reload the current WebView.
1514 currentWebView.reload();
1516 // Consume the event.
1518 } else if (menuItemId == R.id.user_agent_firefox_on_android) { // User Agent - Firefox on Android.
1519 // Update the user agent.
1520 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1522 // Reload the current WebView.
1523 currentWebView.reload();
1525 // Consume the event.
1527 } else if (menuItemId == R.id.user_agent_chrome_on_android) { // User Agent - Chrome on Android.
1528 // Update the user agent.
1529 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1531 // Reload the current WebView.
1532 currentWebView.reload();
1534 // Consume the event.
1536 } else if (menuItemId == R.id.user_agent_safari_on_ios) { // User Agent - Safari on iOS.
1537 // Update the user agent.
1538 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1540 // Reload the current WebView.
1541 currentWebView.reload();
1543 // Consume the event.
1545 } else if (menuItemId == R.id.user_agent_firefox_on_linux) { // User Agent - Firefox on Linux.
1546 // Update the user agent.
1547 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1549 // Reload the current WebView.
1550 currentWebView.reload();
1552 // Consume the event.
1554 } else if (menuItemId == R.id.user_agent_chromium_on_linux) { // User Agent - Chromium on Linux.
1555 // Update the user agent.
1556 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1558 // Reload the current WebView.
1559 currentWebView.reload();
1561 // Consume the event.
1563 } else if (menuItemId == R.id.user_agent_firefox_on_windows) { // User Agent - Firefox on Windows.
1564 // Update the user agent.
1565 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1567 // Reload the current WebView.
1568 currentWebView.reload();
1570 // Consume the event.
1572 } else if (menuItemId == R.id.user_agent_chrome_on_windows) { // User Agent - Chrome on Windows.
1573 // Update the user agent.
1574 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1576 // Reload the current WebView.
1577 currentWebView.reload();
1579 // Consume the event.
1581 } else if (menuItemId == R.id.user_agent_edge_on_windows) { // User Agent - Edge on Windows.
1582 // Update the user agent.
1583 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1585 // Reload the current WebView.
1586 currentWebView.reload();
1588 // Consume the event.
1590 } else if (menuItemId == R.id.user_agent_internet_explorer_on_windows) { // User Agent - Internet Explorer on Windows.
1591 // Update the user agent.
1592 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1594 // Reload the current WebView.
1595 currentWebView.reload();
1597 // Consume the event.
1599 } else if (menuItemId == R.id.user_agent_safari_on_macos) { // User Agent - Safari on macOS.
1600 // Update the user agent.
1601 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1603 // Reload the current WebView.
1604 currentWebView.reload();
1606 // Consume the event.
1608 } else if (menuItemId == R.id.user_agent_custom) { // User Agent - Custom.
1609 // Update the user agent.
1610 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1612 // Reload the current WebView.
1613 currentWebView.reload();
1615 // Consume the event.
1617 } else if (menuItemId == R.id.font_size) { // Font size.
1618 // Instantiate the font size dialog.
1619 DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1621 // Show the font size dialog.
1622 fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1624 // Consume the event.
1626 } else if (menuItemId == R.id.swipe_to_refresh) { // Swipe to refresh.
1627 // Toggle the stored status of swipe to refresh.
1628 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1630 // Update the swipe refresh layout.
1631 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
1632 // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
1633 swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
1634 } else { // Swipe to refresh is disabled.
1635 // Disable the swipe refresh layout.
1636 swipeRefreshLayout.setEnabled(false);
1639 // Consume the event.
1641 } else if (menuItemId == R.id.wide_viewport) { // Wide viewport.
1642 // Toggle the viewport.
1643 currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1645 // Consume the event.
1647 } else if (menuItemId == R.id.display_images) { // Display images.
1648 // Toggle the displaying of images.
1649 if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
1650 // Disable loading of images.
1651 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1653 // Reload the website to remove existing images.
1654 currentWebView.reload();
1655 } else { // Images are not currently loaded automatically.
1656 // Enable loading of images. Missing images will be loaded without the need for a reload.
1657 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1660 // Consume the event.
1662 } else if (menuItemId == R.id.dark_webview) { // Dark WebView.
1663 // Check to see if dark WebView is supported by this WebView.
1664 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
1665 // Toggle the dark WebView setting.
1666 if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled.
1667 // Turn off dark WebView.
1668 WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
1669 } else { // Dark WebView is currently disabled.
1670 // Turn on dark WebView.
1671 WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
1675 // Consume the event.
1677 } else if (menuItemId == R.id.find_on_page) { // Find on page.
1678 // Get a handle for the views.
1679 Toolbar toolbar = findViewById(R.id.toolbar);
1680 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1681 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1683 // Set the minimum height of the find on page linear layout to match the toolbar.
1684 findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1686 // Hide the toolbar.
1687 toolbar.setVisibility(View.GONE);
1689 // Show the find on page linear layout.
1690 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1692 // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
1693 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1694 findOnPageEditText.postDelayed(() -> {
1695 // Set the focus on the find on page edit text.
1696 findOnPageEditText.requestFocus();
1698 // Get a handle for the input method manager.
1699 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1701 // Remove the lint warning below that the input method manager might be null.
1702 assert inputMethodManager != null;
1704 // Display the keyboard. `0` sets no input flags.
1705 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1708 // Consume the event.
1710 } else if (menuItemId == R.id.print) { // Print.
1711 // Get a print manager instance.
1712 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1714 // Remove the lint error below that print manager might be null.
1715 assert printManager != null;
1717 // Create a print document adapter from the current WebView.
1718 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1720 // Print the document.
1721 printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
1723 // Consume the event.
1725 } else if (menuItemId == R.id.save_url) { // Save URL.
1726 // Check the download preference.
1727 if (downloadWithExternalApp) { // Download with an external app.
1728 downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
1729 } else { // Handle the download inside of Privacy Browser.
1730 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
1731 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
1732 currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
1735 // Consume the event.
1737 } else if (menuItemId == R.id.save_archive) {
1738 // Instantiate the save dialog.
1739 DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_ARCHIVE, currentWebView.getCurrentUrl(), null, null, null,
1742 // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
1743 saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1746 } else if (menuItemId == R.id.save_image) { // Save image.
1747 // Instantiate the save dialog.
1748 DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, currentWebView.getCurrentUrl(), null, null, null,
1751 // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
1752 saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1754 // Consume the event.
1756 } else if (menuItemId == R.id.add_to_homescreen) { // Add to homescreen.
1757 // Instantiate the create home screen shortcut dialog.
1758 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1759 currentWebView.getFavoriteOrDefaultIcon());
1761 // Show the create home screen shortcut dialog.
1762 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1764 // Consume the event.
1766 } else if (menuItemId == R.id.view_source) { // View source.
1767 // Create an intent to launch the view source activity.
1768 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1770 // Add the variables to the intent.
1771 viewSourceIntent.putExtra(ViewSourceActivityKt.CURRENT_URL, currentWebView.getUrl());
1772 viewSourceIntent.putExtra(ViewSourceActivityKt.USER_AGENT, currentWebView.getSettings().getUserAgentString());
1775 startActivity(viewSourceIntent);
1777 // Consume the event.
1779 } else if (menuItemId == R.id.share_url) { // Share URL.
1780 // Setup the share string.
1781 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1783 // Create the share intent.
1784 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1786 // Add the share string to the intent.
1787 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1789 // Set the MIME type.
1790 shareIntent.setType("text/plain");
1792 // Set the intent to open in a new task.
1793 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1796 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1798 // Consume the event.
1800 } else if (menuItemId == R.id.open_with_app) { // Open with app.
1801 // Open the URL with an outside app.
1802 openWithApp(currentWebView.getUrl());
1804 // Consume the event.
1806 } else if (menuItemId == R.id.open_with_browser) { // Open with browser.
1807 // Open the URL with an outside browser.
1808 openWithBrowser(currentWebView.getUrl());
1810 // Consume the event.
1812 } else if (menuItemId == R.id.add_or_edit_domain) { // Add or edit domain.
1813 // Check if domain settings currently exist.
1814 if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings.
1815 // Reapply the domain settings on returning to `MainWebViewActivity`.
1816 reapplyDomainSettingsOnRestart = true;
1818 // Create an intent to launch the domains activity.
1819 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1821 // Add the extra information to the intent.
1822 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1823 domainsIntent.putExtra("close_on_back", true);
1824 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1826 // Get the current certificate.
1827 SslCertificate sslCertificate = currentWebView.getCertificate();
1829 // Check to see if the SSL certificate is populated.
1830 if (sslCertificate != null) {
1831 // Extract the certificate to strings.
1832 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1833 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1834 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1835 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1836 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1837 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1838 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1839 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1841 // Add the certificate to the intent.
1842 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1843 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1844 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1845 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1846 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1847 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1848 domainsIntent.putExtra("ssl_start_date", startDateLong);
1849 domainsIntent.putExtra("ssl_end_date", endDateLong);
1852 // Check to see if the current IP addresses have been received.
1853 if (currentWebView.hasCurrentIpAddresses()) {
1854 // Add the current IP addresses to the intent.
1855 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1859 startActivity(domainsIntent);
1860 } else { // Add a new domain.
1861 // Apply the new domain settings on returning to `MainWebViewActivity`.
1862 reapplyDomainSettingsOnRestart = true;
1864 // Get the current domain
1865 Uri currentUri = Uri.parse(currentWebView.getUrl());
1866 String currentDomain = currentUri.getHost();
1868 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1869 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1871 // Create the domain and store the database ID.
1872 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1874 // Create an intent to launch the domains activity.
1875 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1877 // Add the extra information to the intent.
1878 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1879 domainsIntent.putExtra("close_on_back", true);
1880 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1882 // Get the current certificate.
1883 SslCertificate sslCertificate = currentWebView.getCertificate();
1885 // Check to see if the SSL certificate is populated.
1886 if (sslCertificate != null) {
1887 // Extract the certificate to strings.
1888 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1889 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1890 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1891 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1892 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1893 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1894 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1895 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1897 // Add the certificate to the intent.
1898 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1899 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1900 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1901 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1902 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1903 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1904 domainsIntent.putExtra("ssl_start_date", startDateLong);
1905 domainsIntent.putExtra("ssl_end_date", endDateLong);
1908 // Check to see if the current IP addresses have been received.
1909 if (currentWebView.hasCurrentIpAddresses()) {
1910 // Add the current IP addresses to the intent.
1911 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1915 startActivity(domainsIntent);
1918 // Consume the event.
1920 } else if (menuItemId == R.id.ad_consent) { // Ad consent.
1921 // Instantiate the ad consent dialog.
1922 DialogFragment adConsentDialogFragment = new AdConsentDialog();
1924 // Display the ad consent dialog.
1925 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1927 // Consume the event.
1929 } else { // There is no match with the options menu. Pass the event up to the parent method.
1930 // Don't consume the event.
1931 return super.onOptionsItemSelected(menuItem);
1935 // removeAllCookies is deprecated, but it is required for API < 21.
1937 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1938 // Get a handle for the shared preferences.
1939 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1941 // Get the menu item ID.
1942 int menuItemId = menuItem.getItemId();
1944 // Run the commands that correspond to the selected menu item.
1945 if (menuItemId == R.id.clear_and_exit) { // Clear and exit.
1946 // Clear and exit Privacy Browser.
1948 } else if (menuItemId == R.id.home) { // Home.
1949 // Load the homepage.
1950 loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1951 } else if (menuItemId == R.id.back) { // Back.
1952 // Check if the WebView can go back.
1953 if (currentWebView.canGoBack()) {
1954 // Get the current web back forward list.
1955 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1957 // Get the previous entry URL.
1958 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
1960 // Apply the domain settings.
1961 applyDomainSettings(currentWebView, previousUrl, false, false, false);
1963 // Load the previous website in the history.
1964 currentWebView.goBack();
1966 } else if (menuItemId == R.id.forward) { // Forward.
1967 // Check if the WebView can go forward.
1968 if (currentWebView.canGoForward()) {
1969 // Get the current web back forward list.
1970 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1972 // Get the next entry URL.
1973 String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
1975 // Apply the domain settings.
1976 applyDomainSettings(currentWebView, nextUrl, false, false, false);
1978 // Load the next website in the history.
1979 currentWebView.goForward();
1981 } else if (menuItemId == R.id.history) { // History.
1982 // Instantiate the URL history dialog.
1983 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
1985 // Show the URL history dialog.
1986 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
1987 } else if (menuItemId == R.id.open) { // Open.
1988 // Instantiate the open file dialog.
1989 DialogFragment openDialogFragment = new OpenDialog();
1991 // Show the open file dialog.
1992 openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
1993 } else if (menuItemId == R.id.requests) { // Requests.
1994 // Populate the resource requests.
1995 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
1997 // Create an intent to launch the Requests activity.
1998 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2000 // Add the block third-party requests status to the intent.
2001 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
2004 startActivity(requestsIntent);
2005 } else if (menuItemId == R.id.downloads) { // Downloads.
2006 // Try the default system download manager.
2008 // Launch the default system Download Manager.
2009 Intent defaultDownloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2011 // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
2012 defaultDownloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2015 startActivity(defaultDownloadManagerIntent);
2016 } catch (Exception defaultDownloadManagerException) {
2017 // Try a generic file manager.
2019 // Create a generic file manager intent.
2020 Intent genericFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2022 // Open the download directory.
2023 genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR);
2025 // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2026 genericFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2029 startActivity(genericFileManagerIntent);
2030 } catch (Exception genericFileManagerException) {
2031 // Try an alternate file manager.
2033 // Create an alternate file manager intent.
2034 Intent alternateFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2036 // Open the download directory.
2037 alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder");
2039 // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2040 alternateFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2042 // Open the alternate file manager.
2043 startActivity(alternateFileManagerIntent);
2044 } catch (Exception alternateFileManagerException) {
2045 // Display a snackbar.
2046 Snackbar.make(currentWebView, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show();
2050 } else if (menuItemId == R.id.domains) { // Domains.
2051 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2052 reapplyDomainSettingsOnRestart = true;
2054 // Launch the domains activity.
2055 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2057 // Add the extra information to the intent.
2058 domainsIntent.putExtra("current_url", currentWebView.getUrl());
2060 // Get the current certificate.
2061 SslCertificate sslCertificate = currentWebView.getCertificate();
2063 // Check to see if the SSL certificate is populated.
2064 if (sslCertificate != null) {
2065 // Extract the certificate to strings.
2066 String issuedToCName = sslCertificate.getIssuedTo().getCName();
2067 String issuedToOName = sslCertificate.getIssuedTo().getOName();
2068 String issuedToUName = sslCertificate.getIssuedTo().getUName();
2069 String issuedByCName = sslCertificate.getIssuedBy().getCName();
2070 String issuedByOName = sslCertificate.getIssuedBy().getOName();
2071 String issuedByUName = sslCertificate.getIssuedBy().getUName();
2072 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2073 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2075 // Add the certificate to the intent.
2076 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2077 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2078 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2079 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2080 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2081 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2082 domainsIntent.putExtra("ssl_start_date", startDateLong);
2083 domainsIntent.putExtra("ssl_end_date", endDateLong);
2086 // Check to see if the current IP addresses have been received.
2087 if (currentWebView.hasCurrentIpAddresses()) {
2088 // Add the current IP addresses to the intent.
2089 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2093 startActivity(domainsIntent);
2094 } else if (menuItemId == R.id.settings) { // Settings.
2095 // Set the flag to reapply app settings on restart when returning from Settings.
2096 reapplyAppSettingsOnRestart = true;
2098 // Set the flag to reapply the domain settings on restart when returning from Settings.
2099 reapplyDomainSettingsOnRestart = true;
2101 // Launch the settings activity.
2102 Intent settingsIntent = new Intent(this, SettingsActivity.class);
2103 startActivity(settingsIntent);
2104 } else if (menuItemId == R.id.import_export) { // Import/Export.
2105 // Create an intent to launch the import/export activity.
2106 Intent importExportIntent = new Intent(this, ImportExportActivity.class);
2109 startActivity(importExportIntent);
2110 } else if (menuItemId == R.id.logcat) { // Logcat.
2111 // Create an intent to launch the logcat activity.
2112 Intent logcatIntent = new Intent(this, LogcatActivity.class);
2115 startActivity(logcatIntent);
2116 } else if (menuItemId == R.id.guide) { // Guide.
2117 // Create an intent to launch the guide activity.
2118 Intent guideIntent = new Intent(this, GuideActivity.class);
2121 startActivity(guideIntent);
2122 } else if (menuItemId == R.id.about) { // About
2123 // Create an intent to launch the about activity.
2124 Intent aboutIntent = new Intent(this, AboutActivity.class);
2126 // Create a string array for the blocklist versions.
2127 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],
2128 ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
2130 // Add the blocklist versions to the intent.
2131 aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
2134 startActivity(aboutIntent);
2137 // Close the navigation drawer.
2138 drawerLayout.closeDrawer(GravityCompat.START);
2143 public void onPostCreate(Bundle savedInstanceState) {
2144 // Run the default commands.
2145 super.onPostCreate(savedInstanceState);
2147 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
2148 actionBarDrawerToggle.syncState();
2152 public void onConfigurationChanged(@NonNull Configuration newConfig) {
2153 // Run the default commands.
2154 super.onConfigurationChanged(newConfig);
2156 // Reload the ad for the free flavor if not in full screen mode.
2157 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2158 // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
2159 View adView = findViewById(R.id.adview);
2161 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2162 // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
2163 AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id));
2166 // `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:
2167 // https://code.google.com/p/android/issues/detail?id=20493#c8
2168 // ActivityCompat.invalidateOptionsMenu(this);
2172 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2173 // Get the hit test result.
2174 final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2176 // Define the URL strings.
2177 final String imageUrl;
2178 final String linkUrl;
2180 // Get handles for the system managers.
2181 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2183 // Remove the lint errors below that the clipboard manager might be null.
2184 assert clipboardManager != null;
2186 // Process the link according to the type.
2187 switch (hitTestResult.getType()) {
2188 // `SRC_ANCHOR_TYPE` is a link.
2189 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2190 // Get the target URL.
2191 linkUrl = hitTestResult.getExtra();
2193 // Set the target URL as the title of the `ContextMenu`.
2194 menu.setHeaderTitle(linkUrl);
2196 // Add an Open in New Tab entry.
2197 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2198 // Load the link URL in a new tab and move to it.
2199 addNewTab(linkUrl, true);
2201 // Consume the event.
2205 // Add an Open in Background entry.
2206 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2207 // Load the link URL in a new tab but do not move to it.
2208 addNewTab(linkUrl, false);
2210 // Consume the event.
2214 // Add an Open with App entry.
2215 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2216 openWithApp(linkUrl);
2218 // Consume the event.
2222 // Add an Open with Browser entry.
2223 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2224 openWithBrowser(linkUrl);
2226 // Consume the event.
2230 // Add a Copy URL entry.
2231 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2232 // Save the link URL in a `ClipData`.
2233 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2235 // Set the `ClipData` as the clipboard's primary clip.
2236 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2238 // Consume the event.
2242 // Add a Save URL entry.
2243 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2244 // Check the download preference.
2245 if (downloadWithExternalApp) { // Download with an external app.
2246 downloadUrlWithExternalApp(linkUrl);
2247 } else { // Handle the download inside of Privacy Browser.
2248 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2249 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2250 currentWebView.getAcceptCookies()).execute(linkUrl);
2253 // Consume the event.
2257 // Add an empty Cancel entry, which by default closes the context menu.
2258 menu.add(R.string.cancel);
2261 // `IMAGE_TYPE` is an image.
2262 case WebView.HitTestResult.IMAGE_TYPE:
2263 // Get the image URL.
2264 imageUrl = hitTestResult.getExtra();
2266 // Remove the incorrect lint warning below that the image URL might be null.
2267 assert imageUrl != null;
2269 // Set the context menu title.
2270 if (imageUrl.startsWith("data:")) { // The image data is contained in within the URL, making it exceedingly long.
2271 // Truncate the image URL before making it the title.
2272 menu.setHeaderTitle(imageUrl.substring(0, 100));
2273 } else { // The image URL does not contain the full image data.
2274 // Set the image URL as the title of the context menu.
2275 menu.setHeaderTitle(imageUrl);
2278 // Add an Open in New Tab entry.
2279 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2280 // Load the image in a new tab.
2281 addNewTab(imageUrl, true);
2283 // Consume the event.
2287 // Add an Open with App entry.
2288 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2289 // Open the image URL with an external app.
2290 openWithApp(imageUrl);
2292 // Consume the event.
2296 // Add an Open with Browser entry.
2297 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2298 // Open the image URL with an external browser.
2299 openWithBrowser(imageUrl);
2301 // Consume the event.
2305 // Add a View Image entry.
2306 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2307 // Load the image in the current tab.
2308 loadUrl(currentWebView, imageUrl);
2310 // Consume the event.
2314 // Add a Save Image entry.
2315 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2316 // Check the download preference.
2317 if (downloadWithExternalApp) { // Download with an external app.
2318 downloadUrlWithExternalApp(imageUrl);
2319 } else { // Handle the download inside of Privacy Browser.
2320 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2321 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2322 currentWebView.getAcceptCookies()).execute(imageUrl);
2325 // Consume the event.
2329 // Add a Copy URL entry.
2330 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2331 // Save the image URL in a clip data.
2332 ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2334 // Set the clip data as the clipboard's primary clip.
2335 clipboardManager.setPrimaryClip(imageTypeClipData);
2337 // Consume the event.
2341 // Add an empty Cancel entry, which by default closes the context menu.
2342 menu.add(R.string.cancel);
2345 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2346 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2347 // Get the image URL.
2348 imageUrl = hitTestResult.getExtra();
2350 // Instantiate a handler.
2351 Handler handler = new Handler();
2353 // Get a message from the handler.
2354 Message message = handler.obtainMessage();
2356 // Request the image details from the last touched node be returned in the message.
2357 currentWebView.requestFocusNodeHref(message);
2359 // Get the link URL from the message data.
2360 linkUrl = message.getData().getString("url");
2362 // Set the link URL as the title of the context menu.
2363 menu.setHeaderTitle(linkUrl);
2365 // Add an Open in New Tab entry.
2366 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2367 // Load the link URL in a new tab and move to it.
2368 addNewTab(linkUrl, true);
2370 // Consume the event.
2374 // Add an Open in Background entry.
2375 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2376 // Lod the link URL in a new tab but do not move to it.
2377 addNewTab(linkUrl, false);
2379 // Consume the event.
2383 // Add an Open Image in New Tab entry.
2384 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2385 // Load the image in a new tab and move to it.
2386 addNewTab(imageUrl, true);
2388 // Consume the event.
2392 // Add an Open with App entry.
2393 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2394 // Open the link URL with an external app.
2395 openWithApp(linkUrl);
2397 // Consume the event.
2401 // Add an Open with Browser entry.
2402 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2403 // Open the link URL with an external browser.
2404 openWithBrowser(linkUrl);
2406 // Consume the event.
2410 // Add a View Image entry.
2411 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2412 // View the image in the current tab.
2413 loadUrl(currentWebView, imageUrl);
2415 // Consume the event.
2419 // Add a Save Image entry.
2420 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2421 // Check the download preference.
2422 if (downloadWithExternalApp) { // Download with an external app.
2423 downloadUrlWithExternalApp(imageUrl);
2424 } else { // Handle the download inside of Privacy Browser.
2425 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2426 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2427 currentWebView.getAcceptCookies()).execute(imageUrl);
2430 // Consume the event.
2434 // Add a Copy URL entry.
2435 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2436 // Save the link URL in a clip data.
2437 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2439 // Set the clip data as the clipboard's primary clip.
2440 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2442 // Consume the event.
2446 // Add a Save URL entry.
2447 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2448 // Check the download preference.
2449 if (downloadWithExternalApp) { // Download with an external app.
2450 downloadUrlWithExternalApp(linkUrl);
2451 } else { // Handle the download inside of Privacy Browser.
2452 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2453 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2454 currentWebView.getAcceptCookies()).execute(linkUrl);
2457 // Consume the event.
2461 // Add an empty Cancel entry, which by default closes the context menu.
2462 menu.add(R.string.cancel);
2465 case WebView.HitTestResult.EMAIL_TYPE:
2466 // Get the target URL.
2467 linkUrl = hitTestResult.getExtra();
2469 // Set the target URL as the title of the `ContextMenu`.
2470 menu.setHeaderTitle(linkUrl);
2472 // Add a Write Email entry.
2473 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2474 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2475 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2477 // Parse the url and set it as the data for the `Intent`.
2478 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2480 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2481 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2485 startActivity(emailIntent);
2486 } catch (ActivityNotFoundException exception) {
2487 // Display a snackbar.
2488 Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
2491 // Consume the event.
2495 // Add a Copy Email Address entry.
2496 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2497 // Save the email address in a `ClipData`.
2498 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2500 // Set the `ClipData` as the clipboard's primary clip.
2501 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2503 // Consume the event.
2507 // Add an empty Cancel entry, which by default closes the context menu.
2508 menu.add(R.string.cancel);
2514 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2515 // Get a handle for the bookmarks list view.
2516 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2519 Dialog dialog = dialogFragment.getDialog();
2521 // Remove the incorrect lint warning below that the dialog might be null.
2522 assert dialog != null;
2524 // Get the views from the dialog fragment.
2525 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2526 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2528 // Extract the strings from the edit texts.
2529 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2530 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2532 // Create a favorite icon byte array output stream.
2533 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2535 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2536 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2538 // Convert the favorite icon byte array stream to a byte array.
2539 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2541 // Display the new bookmark below the current items in the (0 indexed) list.
2542 int newBookmarkDisplayOrder = bookmarksListView.getCount();
2544 // Create the bookmark.
2545 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2547 // Update the bookmarks cursor with the current contents of this folder.
2548 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2550 // Update the list view.
2551 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2553 // Scroll to the new bookmark.
2554 bookmarksListView.setSelection(newBookmarkDisplayOrder);
2558 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
2559 // Get a handle for the bookmarks list view.
2560 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2563 Dialog dialog = dialogFragment.getDialog();
2565 // Remove the incorrect lint warning below that the dialog might be null.
2566 assert dialog != null;
2568 // Get handles for the views in the dialog fragment.
2569 EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2570 RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2571 ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2573 // Get new folder name string.
2574 String folderNameString = folderNameEditText.getText().toString();
2576 // Create a folder icon bitmap.
2577 Bitmap folderIconBitmap;
2579 // Set the folder icon bitmap according to the dialog.
2580 if (defaultIconRadioButton.isChecked()) { // Use the default folder icon.
2581 // Get the default folder icon drawable.
2582 Drawable folderIconDrawable = defaultIconImageView.getDrawable();
2584 // Convert the folder icon drawable to a bitmap drawable.
2585 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2587 // Convert the folder icon bitmap drawable to a bitmap.
2588 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2589 } else { // Use the WebView favorite icon.
2590 // Copy the favorite icon bitmap to the folder icon bitmap.
2591 folderIconBitmap = favoriteIconBitmap;
2594 // Create a folder icon byte array output stream.
2595 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2597 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2598 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2600 // Convert the folder icon byte array stream to a byte array.
2601 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2603 // Move all the bookmarks down one in the display order.
2604 for (int i = 0; i < bookmarksListView.getCount(); i++) {
2605 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2606 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2609 // Create the folder, which will be placed at the top of the `ListView`.
2610 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2612 // Update the bookmarks cursor with the current contents of this folder.
2613 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2615 // Update the `ListView`.
2616 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2618 // Scroll to the new folder.
2619 bookmarksListView.setSelection(0);
2623 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, @NonNull Bitmap favoriteIconBitmap) {
2624 // Remove the incorrect lint warning below that the dialog fragment might be null.
2625 assert dialogFragment != null;
2628 Dialog dialog = dialogFragment.getDialog();
2630 // Remove the incorrect lint warning below that the dialog might be null.
2631 assert dialog != null;
2633 // Get handles for the views from the dialog.
2634 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.current_icon_radiobutton);
2635 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2636 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2637 EditText editFolderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2639 // Get the new folder name.
2640 String newFolderNameString = editFolderNameEditText.getText().toString();
2642 // Check if the favorite icon has changed.
2643 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
2644 // Update the name in the database.
2645 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2646 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
2647 // Create the new folder icon Bitmap.
2648 Bitmap folderIconBitmap;
2650 // Populate the new folder icon bitmap.
2651 if (defaultFolderIconRadioButton.isChecked()) {
2652 // Get the default folder icon drawable.
2653 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2655 // Convert the folder icon drawable to a bitmap drawable.
2656 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2658 // Convert the folder icon bitmap drawable to a bitmap.
2659 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2660 } else { // Use the `WebView` favorite icon.
2661 // Copy the favorite icon bitmap to the folder icon bitmap.
2662 folderIconBitmap = favoriteIconBitmap;
2665 // Create a folder icon byte array output stream.
2666 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2668 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2669 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2671 // Convert the folder icon byte array stream to a byte array.
2672 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2674 // Update the folder icon in the database.
2675 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2676 } else { // The folder icon and the name have changed.
2677 // Get the new folder icon bitmap.
2678 Bitmap folderIconBitmap;
2679 if (defaultFolderIconRadioButton.isChecked()) {
2680 // Get the default folder icon drawable.
2681 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2683 // Convert the folder icon drawable to a bitmap drawable.
2684 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2686 // Convert the folder icon bitmap drawable to a bitmap.
2687 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2688 } else { // Use the `WebView` favorite icon.
2689 // Copy the favorite icon bitmap to the folder icon bitmap.
2690 folderIconBitmap = favoriteIconBitmap;
2693 // Create a folder icon byte array output stream.
2694 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2696 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2697 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2699 // Convert the folder icon byte array stream to a byte array.
2700 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2702 // Update the folder name and icon in the database.
2703 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2706 // Update the bookmarks cursor with the current contents of this folder.
2707 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2709 // Update the `ListView`.
2710 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2713 // Override `onBackPressed()` to handle the navigation drawer and and the WebViews.
2715 public void onBackPressed() {
2716 // Check the different options for processing `back`.
2717 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
2718 // Close the navigation drawer.
2719 drawerLayout.closeDrawer(GravityCompat.START);
2720 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
2721 // close the bookmarks drawer.
2722 drawerLayout.closeDrawer(GravityCompat.END);
2723 } else if (displayingFullScreenVideo) { // A full screen video is shown.
2724 // Exit the full screen video.
2725 exitFullScreenVideo();
2726 } else if (currentWebView.canGoBack()) { // There is at least one item in the current WebView history.
2727 // Get the current web back forward list.
2728 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
2730 // Get the previous entry URL.
2731 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
2733 // Apply the domain settings.
2734 applyDomainSettings(currentWebView, previousUrl, false, false, false);
2737 currentWebView.goBack();
2738 } else if (tabLayout.getTabCount() > 1) { // There are at least two tabs.
2739 // Close the current tab.
2741 } else { // There isn't anything to do in Privacy Browser.
2742 // Close Privacy Browser. `finishAndRemoveTask()` also removes Privacy Browser from the recent app list.
2743 if (Build.VERSION.SDK_INT >= 21) {
2744 finishAndRemoveTask();
2749 // Manually kill Privacy Browser. Otherwise, it is glitchy when restarted.
2754 // Process the results of a file browse.
2756 public void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
2757 // Run the default commands.
2758 super.onActivityResult(requestCode, resultCode, returnedIntent);
2760 // Run the commands that correlate to the specified request code.
2761 switch (requestCode) {
2762 case BROWSE_FILE_UPLOAD_REQUEST_CODE:
2763 // File uploads only work on API >= 21.
2764 if (Build.VERSION.SDK_INT >= 21) {
2765 // Pass the file to the WebView.
2766 fileChooserCallback.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, returnedIntent));
2770 case BROWSE_OPEN_REQUEST_CODE:
2771 // Don't do anything if the user pressed back from the file picker.
2772 if (resultCode == Activity.RESULT_OK) {
2773 // Get a handle for the open dialog fragment.
2774 DialogFragment openDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.open));
2776 // Only update the file name if the dialog still exists.
2777 if (openDialogFragment != null) {
2778 // Get a handle for the open dialog.
2779 Dialog openDialog = openDialogFragment.getDialog();
2781 // Remove the incorrect lint warning below that the dialog might be null.
2782 assert openDialog != null;
2784 // Get a handle for the file name edit text.
2785 EditText fileNameEditText = openDialog.findViewById(R.id.file_name_edittext);
2787 // Get the file name URI from the intent.
2788 Uri fileNameUri = returnedIntent.getData();
2790 // Get the file name string from the URI.
2791 String fileNameString = fileNameUri.toString();
2793 // Set the file name text.
2794 fileNameEditText.setText(fileNameString);
2796 // Move the cursor to the end of the file name edit text.
2797 fileNameEditText.setSelection(fileNameString.length());
2802 case BROWSE_SAVE_WEBPAGE_REQUEST_CODE:
2803 // Don't do anything if the user pressed back from the file picker.
2804 if (resultCode == Activity.RESULT_OK) {
2805 // Get a handle for the save dialog fragment.
2806 DialogFragment saveWebpageDialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(getString(R.string.save_dialog));
2808 // Only update the file name if the dialog still exists.
2809 if (saveWebpageDialogFragment != null) {
2810 // Get a handle for the save webpage dialog.
2811 Dialog saveWebpageDialog = saveWebpageDialogFragment.getDialog();
2813 // Remove the incorrect lint warning below that the dialog might be null.
2814 assert saveWebpageDialog != null;
2816 // Get a handle for the file name edit text.
2817 EditText fileNameEditText = saveWebpageDialog.findViewById(R.id.file_name_edittext);
2819 // Get the file name URI from the intent.
2820 Uri fileNameUri = returnedIntent.getData();
2822 // Get the file name string from the URI.
2823 String fileNameString = fileNameUri.toString();
2825 // Set the file name text.
2826 fileNameEditText.setText(fileNameString);
2828 // Move the cursor to the end of the file name edit text.
2829 fileNameEditText.setSelection(fileNameString.length());
2836 private void loadUrlFromTextBox() {
2837 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
2838 String unformattedUrlString = urlEditText.getText().toString().trim();
2840 // Initialize the formatted URL string.
2843 // Check to see if `unformattedUrlString` is a valid URL. Otherwise, convert it into a search.
2844 if (unformattedUrlString.startsWith("content://")) { // This is a Content URL.
2845 // Load the entire content URL.
2846 url = unformattedUrlString;
2847 } else if (Patterns.WEB_URL.matcher(unformattedUrlString).matches() || unformattedUrlString.startsWith("http://") || unformattedUrlString.startsWith("https://") ||
2848 unformattedUrlString.startsWith("file://")) { // This is a standard URL.
2849 // Add `https://` at the beginning if there is no protocol. Otherwise the app will segfault.
2850 if (!unformattedUrlString.startsWith("http") && !unformattedUrlString.startsWith("file://") && !unformattedUrlString.startsWith("content://")) {
2851 unformattedUrlString = "https://" + unformattedUrlString;
2854 // Initialize `unformattedUrl`.
2855 URL unformattedUrl = null;
2857 // 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.
2859 unformattedUrl = new URL(unformattedUrlString);
2860 } catch (MalformedURLException e) {
2861 e.printStackTrace();
2864 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if `.get` was called on a `null` value.
2865 String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
2866 String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
2867 String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
2868 String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
2869 String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
2872 Uri.Builder uri = new Uri.Builder();
2873 uri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
2875 // Decode the URI as a UTF-8 string in.
2877 url = URLDecoder.decode(uri.build().toString(), "UTF-8");
2878 } catch (UnsupportedEncodingException exception) {
2879 // Do nothing. The formatted URL string will remain blank.
2881 } else if (!unformattedUrlString.isEmpty()){ // This is not a URL, but rather a search string.
2882 // Create an encoded URL String.
2883 String encodedUrlString;
2885 // Sanitize the search input.
2887 encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
2888 } catch (UnsupportedEncodingException exception) {
2889 encodedUrlString = "";
2892 // Add the base search URL.
2893 url = searchURL + encodedUrlString;
2896 // 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.
2897 urlEditText.clearFocus();
2900 loadUrl(currentWebView, url);
2903 private void loadUrl(NestedScrollWebView nestedScrollWebView, String url) {
2904 // Sanitize the URL.
2905 url = sanitizeUrl(url);
2907 // Apply the domain settings and load the URL.
2908 applyDomainSettings(nestedScrollWebView, url, true, false, true);
2911 public void findPreviousOnPage(View view) {
2912 // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards.
2913 currentWebView.findNext(false);
2916 public void findNextOnPage(View view) {
2917 // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
2918 currentWebView.findNext(true);
2921 public void closeFindOnPage(View view) {
2922 // Get a handle for the views.
2923 Toolbar toolbar = findViewById(R.id.toolbar);
2924 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
2925 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
2927 // Delete the contents of `find_on_page_edittext`.
2928 findOnPageEditText.setText(null);
2930 // Clear the highlighted phrases if the WebView is not null.
2931 if (currentWebView != null) {
2932 currentWebView.clearMatches();
2935 // Hide the find on page linear layout.
2936 findOnPageLinearLayout.setVisibility(View.GONE);
2938 // Show the toolbar.
2939 toolbar.setVisibility(View.VISIBLE);
2941 // Get a handle for the input method manager.
2942 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
2944 // Remove the lint warning below that the input method manager might be null.
2945 assert inputMethodManager != null;
2947 // Hide the keyboard.
2948 inputMethodManager.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
2952 public void onApplyNewFontSize(DialogFragment dialogFragment) {
2953 // Remove the incorrect lint warning below that the dialog fragment might be null.
2954 assert dialogFragment != null;