2 * Copyright © 2015-2020 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.Dialog;
28 import android.app.DownloadManager;
29 import android.app.SearchManager;
30 import android.content.ActivityNotFoundException;
31 import android.content.BroadcastReceiver;
32 import android.content.ClipData;
33 import android.content.ClipboardManager;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.SharedPreferences;
38 import android.content.pm.PackageManager;
39 import android.content.res.Configuration;
40 import android.database.Cursor;
41 import android.graphics.Bitmap;
42 import android.graphics.BitmapFactory;
43 import android.graphics.Typeface;
44 import android.graphics.drawable.BitmapDrawable;
45 import android.graphics.drawable.Drawable;
46 import android.net.Uri;
47 import android.net.http.SslCertificate;
48 import android.net.http.SslError;
49 import android.os.AsyncTask;
50 import android.os.Build;
51 import android.os.Bundle;
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.text.Editable;
58 import android.text.Spanned;
59 import android.text.TextWatcher;
60 import android.text.style.ForegroundColorSpan;
61 import android.util.Patterns;
62 import android.util.TypedValue;
63 import android.view.ContextMenu;
64 import android.view.GestureDetector;
65 import android.view.KeyEvent;
66 import android.view.Menu;
67 import android.view.MenuItem;
68 import android.view.MotionEvent;
69 import android.view.View;
70 import android.view.ViewGroup;
71 import android.view.WindowManager;
72 import android.view.inputmethod.InputMethodManager;
73 import android.webkit.CookieManager;
74 import android.webkit.HttpAuthHandler;
75 import android.webkit.SslErrorHandler;
76 import android.webkit.ValueCallback;
77 import android.webkit.WebBackForwardList;
78 import android.webkit.WebChromeClient;
79 import android.webkit.WebResourceResponse;
80 import android.webkit.WebSettings;
81 import android.webkit.WebStorage;
82 import android.webkit.WebView;
83 import android.webkit.WebViewClient;
84 import android.webkit.WebViewDatabase;
85 import android.widget.ArrayAdapter;
86 import android.widget.CursorAdapter;
87 import android.widget.EditText;
88 import android.widget.FrameLayout;
89 import android.widget.ImageView;
90 import android.widget.LinearLayout;
91 import android.widget.ListView;
92 import android.widget.ProgressBar;
93 import android.widget.RadioButton;
94 import android.widget.RelativeLayout;
95 import android.widget.TextView;
97 import androidx.annotation.NonNull;
98 import androidx.appcompat.app.ActionBar;
99 import androidx.appcompat.app.ActionBarDrawerToggle;
100 import androidx.appcompat.app.AppCompatActivity;
101 import androidx.appcompat.app.AppCompatDelegate;
102 import androidx.appcompat.widget.Toolbar;
103 import androidx.coordinatorlayout.widget.CoordinatorLayout;
104 import androidx.core.app.ActivityCompat;
105 import androidx.core.content.ContextCompat;
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.SaveDialog;
140 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
141 import com.stoutner.privacybrowser.dialogs.StoragePermissionDialog;
142 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
143 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
144 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
145 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
146 import com.stoutner.privacybrowser.helpers.AdHelper;
147 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
148 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
149 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
150 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
151 import com.stoutner.privacybrowser.helpers.FileNameHelper;
152 import com.stoutner.privacybrowser.helpers.ProxyHelper;
153 import com.stoutner.privacybrowser.views.NestedScrollWebView;
155 import java.io.ByteArrayInputStream;
156 import java.io.ByteArrayOutputStream;
158 import java.io.IOException;
159 import java.io.UnsupportedEncodingException;
160 import java.net.MalformedURLException;
162 import java.net.URLDecoder;
163 import java.net.URLEncoder;
164 import java.text.NumberFormat;
165 import java.util.ArrayList;
166 import java.util.Date;
167 import java.util.HashMap;
168 import java.util.HashSet;
169 import java.util.List;
170 import java.util.Map;
171 import java.util.Objects;
172 import java.util.Set;
173 import java.util.concurrent.ExecutorService;
174 import java.util.concurrent.Executors;
176 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
177 EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
178 PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveDialog.SaveWebpageListener, StoragePermissionDialog.StoragePermissionDialogListener,
179 UrlHistoryDialog.NavigateHistoryListener, WebViewTabFragment.NewTabListener {
181 // The executor service handles background tasks. It is accessed from `ViewSourceActivity`.
182 public static ExecutorService executorService = Executors.newFixedThreadPool(4);
184 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
185 public static String orbotStatus = "unknown";
187 // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`.
188 public static WebViewPagerAdapter webViewPagerAdapter;
190 // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`. They are used in `onRestart()`.
191 public static boolean loadUrlOnRestart;
192 public static String urlToLoadOnRestart;
194 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
195 public static boolean restartFromBookmarksActivity;
197 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
198 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
199 public static String currentBookmarksFolder;
201 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
202 public final static int UNRECOGNIZED_USER_AGENT = -1;
203 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
204 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
205 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
206 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
207 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
209 // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
210 public final static int BROWSE_OPEN_REQUEST_CODE = 0;
211 public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1;
212 private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2;
214 // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
215 // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
216 // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
217 public static String proxyMode = ProxyHelper.NONE;
220 // The permission result request codes are used in `onCreateContextMenu()`, `onRequestPermissionResult()`, `onSaveWebpage()`, `onCloseStoragePermissionDialog()`, and `initializeWebView()`.
221 private final int PERMISSION_OPEN_REQUEST_CODE = 0;
222 private final int PERMISSION_SAVE_URL_REQUEST_CODE = 1;
223 private final int PERMISSION_SAVE_AS_ARCHIVE_REQUEST_CODE = 2;
224 private final int PERMISSION_SAVE_AS_IMAGE_REQUEST_CODE = 3;
226 // Define the saved instance state constants.
227 private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
228 private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
229 private final String SAVED_TAB_POSITION = "saved_tab_position";
230 private final String PROXY_MODE = "proxy_mode";
232 // Define the saved instance state variables.
233 private ArrayList<Bundle> savedStateArrayList;
234 private ArrayList<Bundle> savedNestedScrollWebViewStateArrayList;
235 private int savedTabPosition;
236 private String savedProxyMode;
238 // Define the class variables.
239 @SuppressWarnings("rawtypes")
240 AsyncTask populateBlocklists;
242 // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
243 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
244 private NestedScrollWebView currentWebView;
246 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
247 private final Map<String, String> customHeaders = new HashMap<>();
249 // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
250 private String searchURL;
252 // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
253 private Menu optionsMenu;
255 // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
256 private ArrayList<List<String[]>> easyList;
257 private ArrayList<List<String[]>> easyPrivacy;
258 private ArrayList<List<String[]>> fanboysAnnoyanceList;
259 private ArrayList<List<String[]>> fanboysSocialList;
260 private ArrayList<List<String[]>> ultraList;
261 private ArrayList<List<String[]>> ultraPrivacy;
263 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
264 private String webViewDefaultUserAgent;
266 // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
267 private boolean incognitoModeEnabled;
269 // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
270 private boolean fullScreenBrowsingModeEnabled;
272 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
273 private boolean inFullScreenBrowsingMode;
275 // The app bar trackers are set in `applyAppSettings()` and used in `initializeWebView()`.
276 private boolean hideAppBar;
277 private boolean scrollAppBar;
279 // The loading new intent tracker is set in `onNewIntent()` and used in `setCurrentWebView()`.
280 private boolean loadingNewIntent;
282 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
283 private boolean reapplyDomainSettingsOnRestart;
285 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
286 private boolean reapplyAppSettingsOnRestart;
288 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
289 private boolean displayingFullScreenVideo;
291 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
292 private BroadcastReceiver orbotStatusBroadcastReceiver;
294 // The waiting for proxy boolean is used in `onResume()`, `initializeApp()` and `applyProxy()`.
295 private boolean waitingForProxy = false;
297 // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
298 private ActionBarDrawerToggle actionBarDrawerToggle;
300 // The color spans are used in `onCreate()` and `highlightUrlText()`.
301 private ForegroundColorSpan redColorSpan;
302 private ForegroundColorSpan initialGrayColorSpan;
303 private ForegroundColorSpan finalGrayColorSpan;
305 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
306 // and `loadBookmarksFolder()`.
307 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
309 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
310 private Cursor bookmarksCursor;
312 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
313 private CursorAdapter bookmarksCursorAdapter;
315 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
316 private String oldFolderNameString;
318 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
319 private ValueCallback<Uri[]> fileChooserCallback;
321 // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
322 private int appBarHeight;
323 private int defaultProgressViewStartOffset;
324 private int defaultProgressViewEndOffset;
326 // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
327 private boolean sanitizeGoogleAnalytics;
328 private boolean sanitizeFacebookClickIds;
329 private boolean sanitizeTwitterAmpRedirects;
331 // The file path strings are used in `onSaveWebpage()` and `onRequestPermissionResult()`
332 private String openFilePath;
333 private String saveWebpageUrl;
334 private String saveWebpageFilePath;
336 // Declare the class views.
337 private DrawerLayout drawerLayout;
338 private AppBarLayout appBarLayout;
339 private TabLayout tabLayout;
340 private ViewPager webViewPager;
343 // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
344 @SuppressLint("ClickableViewAccessibility")
345 protected void onCreate(Bundle savedInstanceState) {
346 // Run the default commands.
347 super.onCreate(savedInstanceState);
349 // Check to see if the activity has been restarted.
350 if (savedInstanceState != null) {
351 // Store the saved instance state variables.
352 savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
353 savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
354 savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
355 savedProxyMode = savedInstanceState.getString(PROXY_MODE);
358 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
359 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
361 // Get a handle for the shared preferences.
362 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
364 // Get the screenshot preference.
365 String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
366 boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
368 // Get the theme entry values string array.
369 String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
371 // 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.
372 if (appTheme.equals(appThemeEntryValuesStringArray[1])) { // The light theme is selected.
373 // Apply the light theme.
374 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
375 } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) { // The dark theme is selected.
376 // Apply the dark theme.
377 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
378 } else { // The system default theme is selected.
379 if (Build.VERSION.SDK_INT >= 28) { // The system default theme is supported.
380 // Follow the system default theme.
381 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
382 } else { // The system default theme is not supported.
383 // Follow the battery saver mode.
384 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
388 // Disable screenshots if not allowed.
389 if (!allowScreenshots) {
390 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
393 // 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.
394 if (Build.VERSION.SDK_INT >= 21) {
395 WebView.enableSlowWholeDocumentDraw();
399 setTheme(R.style.PrivacyBrowser);
401 // Set the content view.
402 setContentView(R.layout.main_framelayout);
404 // Get handles for the views.
405 drawerLayout = findViewById(R.id.drawerlayout);
406 appBarLayout = findViewById(R.id.appbar_layout);
407 Toolbar toolbar = findViewById(R.id.toolbar);
408 tabLayout = findViewById(R.id.tablayout);
409 webViewPager = findViewById(R.id.webviewpager);
411 // Get a handle for the app compat delegate.
412 AppCompatDelegate appCompatDelegate = getDelegate();
414 // Set the support action bar.
415 appCompatDelegate.setSupportActionBar(toolbar);
417 // Get a handle for the action bar.
418 ActionBar actionBar = appCompatDelegate.getSupportActionBar();
420 // This is needed to get rid of the Android Studio warning that the action bar might be null.
421 assert actionBar != null;
423 // Add the custom layout, which shows the URL text bar.
424 actionBar.setCustomView(R.layout.url_app_bar);
425 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
427 // Create the hamburger icon at the start of the AppBar.
428 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
430 // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded.
431 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
433 // Initialize the web view pager adapter.
434 webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
436 // Set the pager adapter on the web view pager.
437 webViewPager.setAdapter(webViewPagerAdapter);
439 // Store up to 100 tabs in memory.
440 webViewPager.setOffscreenPageLimit(100);
442 // Initialize the app.
445 // Apply the app settings from the shared preferences.
448 // Populate the blocklists.
449 populateBlocklists = new PopulateBlocklists(this, this).execute();
453 protected void onNewIntent(Intent intent) {
454 // Run the default commands.
455 super.onNewIntent(intent);
457 // Replace the intent that started the app with this one.
460 // Check to see if the app is being restarted.
461 if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is running for the first time.
462 // Get the information from the intent.
463 String intentAction = intent.getAction();
464 Uri intentUriData = intent.getData();
466 // Determine if this is a web search.
467 boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
469 // 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.
470 if (intentUriData != null || isWebSearch) {
471 // Get the shared preferences.
472 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
474 // Create a URL string.
477 // If the intent action is a web search, perform the search.
478 if (isWebSearch) { // The intent is a web search.
479 // Create an encoded URL string.
480 String encodedUrlString;
482 // Sanitize the search input and convert it to a search.
484 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
485 } catch (UnsupportedEncodingException exception) {
486 encodedUrlString = "";
489 // Add the base search URL.
490 url = searchURL + encodedUrlString;
491 } else { // The intent should contain a URL.
492 // Set the intent data as the URL.
493 url = intentUriData.toString();
496 // Add a new tab if specified in the preferences.
497 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
498 // Set the loading new intent flag.
499 loadingNewIntent = true;
502 addNewTab(url, true);
503 } else { // Load the URL in the current tab.
505 loadUrl(currentWebView, url);
508 // Close the navigation drawer if it is open.
509 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
510 drawerLayout.closeDrawer(GravityCompat.START);
513 // Close the bookmarks drawer if it is open.
514 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
515 drawerLayout.closeDrawer(GravityCompat.END);
522 public void onRestart() {
523 // Run the default commands.
526 // Apply the app settings if returning from the Settings activity.
527 if (reapplyAppSettingsOnRestart) {
528 // Reset the reapply app settings on restart tracker.
529 reapplyAppSettingsOnRestart = false;
531 // Apply the app settings.
535 // Apply the domain settings if returning from the settings or domains activity.
536 if (reapplyDomainSettingsOnRestart) {
537 // Reset the reapply domain settings on restart tracker.
538 reapplyDomainSettingsOnRestart = false;
540 // Reapply the domain settings for each tab.
541 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
542 // Get the WebView tab fragment.
543 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
545 // Get the fragment view.
546 View fragmentView = webViewTabFragment.getView();
548 // Only reload the WebViews if they exist.
549 if (fragmentView != null) {
550 // Get the nested scroll WebView from the tab fragment.
551 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
553 // Reset the current domain name so the domain settings will be reapplied.
554 nestedScrollWebView.resetCurrentDomainName();
556 // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
557 if (nestedScrollWebView.getUrl() != null) {
558 applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
564 // Load the URL on restart (used when loading a bookmark).
565 if (loadUrlOnRestart) {
566 // Load the specified URL.
567 loadUrl(currentWebView, urlToLoadOnRestart);
569 // Reset the load on restart tracker.
570 loadUrlOnRestart = false;
573 // Update the bookmarks drawer if returning from the Bookmarks activity.
574 if (restartFromBookmarksActivity) {
575 // Close the bookmarks drawer.
576 drawerLayout.closeDrawer(GravityCompat.END);
578 // Reload the bookmarks drawer.
579 loadBookmarksFolder();
581 // Reset `restartFromBookmarksActivity`.
582 restartFromBookmarksActivity = false;
585 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
586 updatePrivacyIcons(true);
589 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
591 public void onResume() {
592 // Run the default commands.
595 // Resume any WebViews.
596 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
597 // Get the WebView tab fragment.
598 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
600 // Get the fragment view.
601 View fragmentView = webViewTabFragment.getView();
603 // Only resume the WebViews if they exist (they won't when the app is first created).
604 if (fragmentView != null) {
605 // Get the nested scroll WebView from the tab fragment.
606 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
608 // Resume the nested scroll WebView JavaScript timers.
609 nestedScrollWebView.resumeTimers();
611 // Resume the nested scroll WebView.
612 nestedScrollWebView.onResume();
616 // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog.
617 if (!proxyMode.equals(ProxyHelper.NONE)) {
621 // Reapply any system UI flags and the ad in the free flavor.
622 if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode.
623 // Get a handle for the root frame layouts.
624 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
626 /* Hide the system bars.
627 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
628 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
629 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
630 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
632 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
633 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
634 } else if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode.
636 AdHelper.resumeAd(findViewById(R.id.adview));
641 public void onPause() {
642 // Run the default commands.
645 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
646 // Get the WebView tab fragment.
647 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
649 // Get the fragment view.
650 View fragmentView = webViewTabFragment.getView();
652 // Only pause the WebViews if they exist (they won't when the app is first created).
653 if (fragmentView != null) {
654 // Get the nested scroll WebView from the tab fragment.
655 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
657 // Pause the nested scroll WebView.
658 nestedScrollWebView.onPause();
660 // Pause the nested scroll WebView JavaScript timers.
661 nestedScrollWebView.pauseTimers();
665 // Pause the ad or it will continue to consume resources in the background on the free flavor.
666 if (BuildConfig.FLAVOR.contentEquals("free")) {
668 AdHelper.pauseAd(findViewById(R.id.adview));
673 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
674 // Run the default commands.
675 super.onSaveInstanceState(savedInstanceState);
677 // Create the saved state array lists.
678 ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
679 ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
681 // Get the URLs from each tab.
682 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
683 // Get the WebView tab fragment.
684 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
686 // Get the fragment view.
687 View fragmentView = webViewTabFragment.getView();
689 if (fragmentView != null) {
690 // Get the nested scroll WebView from the tab fragment.
691 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
693 // Create saved state bundle.
694 Bundle savedStateBundle = new Bundle();
696 // Get the current states.
697 nestedScrollWebView.saveState(savedStateBundle);
698 Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
700 // Store the saved states in the array lists.
701 savedStateArrayList.add(savedStateBundle);
702 savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
706 // Get the current tab position.
707 int currentTabPosition = tabLayout.getSelectedTabPosition();
709 // Store the saved states in the bundle.
710 savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
711 savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
712 savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
713 savedInstanceState.putString(PROXY_MODE, proxyMode);
717 public void onDestroy() {
718 // Unregister the orbot status broadcast receiver if it exists.
719 if (orbotStatusBroadcastReceiver != null) {
720 this.unregisterReceiver(orbotStatusBroadcastReceiver);
723 // Close the bookmarks cursor if it exists.
724 if (bookmarksCursor != null) {
725 bookmarksCursor.close();
728 // Close the bookmarks database if it exists.
729 if (bookmarksDatabaseHelper != null) {
730 bookmarksDatabaseHelper.close();
733 // Stop populating the blocklists if the AsyncTask is running in the background.
734 if (populateBlocklists != null) {
735 populateBlocklists.cancel(true);
738 // Run the default commands.
743 public boolean onCreateOptionsMenu(Menu menu) {
744 // Inflate the menu. This adds items to the action bar if it is present.
745 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
747 // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
750 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
751 updatePrivacyIcons(false);
753 // Get handles for the menu items.
754 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
755 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
756 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
757 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
758 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
759 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
760 MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview);
761 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
763 // Only display third-party cookies if API >= 21
764 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
766 // Only display the form data menu items if the API < 26.
767 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
768 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
770 // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
771 clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
773 // Only display the dark WebView menu item if API >= 21.
774 darkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
776 // Only show Ad Consent if this is the free flavor.
777 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
779 // Get the shared preferences.
780 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
782 // Get the dark theme and app bar preferences..
783 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
785 // 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.
786 if (displayAdditionalAppBarIcons) {
787 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
788 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
789 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
790 } else { //Do not display the additional icons.
791 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
792 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
793 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
796 // Replace Refresh with Stop if a URL is already loading.
797 if (currentWebView != null && currentWebView.getProgress() != 100) {
799 refreshMenuItem.setTitle(R.string.stop);
801 // Set the icon if it is displayed in the app bar.
802 if (displayAdditionalAppBarIcons) {
803 // Get the current theme status.
804 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
806 // Set the icon according to the current theme status.
807 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
808 refreshMenuItem.setIcon(R.drawable.close_day);
810 refreshMenuItem.setIcon(R.drawable.close_night);
820 public boolean onPrepareOptionsMenu(Menu menu) {
821 // Get handles for the menu items.
822 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
823 MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
824 MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
825 MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
826 MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
827 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
828 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
829 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
830 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
831 MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
832 MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
833 MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
834 MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
835 MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
836 MenuItem ultraListMenuItem = menu.findItem(R.id.ultralist);
837 MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
838 MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
839 MenuItem proxyMenuItem = menu.findItem(R.id.proxy);
840 MenuItem userAgentMenuItem = menu.findItem(R.id.user_agent);
841 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
842 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
843 MenuItem wideViewportMenuItem = menu.findItem(R.id.wide_viewport);
844 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
845 MenuItem darkWebViewMenuItem = menu.findItem(R.id.dark_webview);
847 // Get a handle for the cookie manager.
848 CookieManager cookieManager = CookieManager.getInstance();
850 // Initialize the current user agent string and the font size.
851 String currentUserAgent = getString(R.string.user_agent_privacy_browser);
854 // 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.
855 if (currentWebView != null) {
856 // Set the add or edit domain text.
857 if (currentWebView.getDomainSettingsApplied()) {
858 addOrEditDomain.setTitle(R.string.edit_domain_settings);
860 addOrEditDomain.setTitle(R.string.add_domain_settings);
863 // Get the current user agent from the WebView.
864 currentUserAgent = currentWebView.getSettings().getUserAgentString();
866 // Get the current font size from the
867 fontSize = currentWebView.getSettings().getTextZoom();
869 // Set the status of the menu item checkboxes.
870 domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
871 saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
872 easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
873 easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
874 fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
875 fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
876 ultraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
877 ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
878 blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
879 swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
880 wideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
881 displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
883 // Initialize the display names for the blocklists with the number of blocked requests.
884 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
885 easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
886 easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
887 fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
888 fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
889 ultraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
890 ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
891 blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
893 // Only modify third-party cookies if the API >= 21.
894 if (Build.VERSION.SDK_INT >= 21) {
895 // Set the status of the third-party cookies checkbox.
896 thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
898 // Enable third-party cookies if first-party cookies are enabled.
899 thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
902 // Enable DOM Storage if JavaScript is enabled.
903 domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
905 // Set the checkbox status for dark WebView if the WebView supports it.
906 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
907 darkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
911 // Set the checked status of the first party cookies menu item.
912 firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
914 // Enable Clear Cookies if there are any.
915 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
917 // 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`.
918 String privateDataDirectoryString = getApplicationInfo().dataDir;
920 // Get a count of the number of files in the Local Storage directory.
921 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
922 int localStorageDirectoryNumberOfFiles = 0;
923 if (localStorageDirectory.exists()) {
924 // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
925 localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
928 // Get a count of the number of files in the IndexedDB directory.
929 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
930 int indexedDBDirectoryNumberOfFiles = 0;
931 if (indexedDBDirectory.exists()) {
932 // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
933 indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
936 // Enable Clear DOM Storage if there is any.
937 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
939 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
940 if (Build.VERSION.SDK_INT < 26) {
941 // Get the WebView database.
942 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
944 // Enable the clear form data menu item if there is anything to clear.
945 clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
948 // Enable Clear Data if any of the submenu items are enabled.
949 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
951 // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
952 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
954 // Set the proxy title and check the applied proxy.
956 case ProxyHelper.NONE:
957 // Set the proxy title.
958 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
960 // Check the proxy None radio button.
961 menu.findItem(R.id.proxy_none).setChecked(true);
964 case ProxyHelper.TOR:
965 // Set the proxy title.
966 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
968 // Check the proxy Tor radio button.
969 menu.findItem(R.id.proxy_tor).setChecked(true);
972 case ProxyHelper.I2P:
973 // Set the proxy title.
974 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
976 // Check the proxy I2P radio button.
977 menu.findItem(R.id.proxy_i2p).setChecked(true);
980 case ProxyHelper.CUSTOM:
981 // Set the proxy title.
982 proxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
984 // Check the proxy Custom radio button.
985 menu.findItem(R.id.proxy_custom).setChecked(true);
989 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
990 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
991 // Update the user agent menu item title.
992 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
994 // Select the Privacy Browser radio box.
995 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
996 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
997 // Update the user agent menu item title.
998 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
1000 // Select the WebView Default radio box.
1001 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
1002 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
1003 // Update the user agent menu item title.
1004 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
1006 // Select the Firefox on Android radio box.
1007 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
1008 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
1009 // Update the user agent menu item title.
1010 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
1012 // Select the Chrome on Android radio box.
1013 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
1014 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
1015 // Update the user agent menu item title.
1016 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
1018 // Select the Safari on iOS radio box.
1019 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
1020 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
1021 // Update the user agent menu item title.
1022 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
1024 // Select the Firefox on Linux radio box.
1025 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
1026 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
1027 // Update the user agent menu item title.
1028 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
1030 // Select the Chromium on Linux radio box.
1031 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
1032 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
1033 // Update the user agent menu item title.
1034 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
1036 // Select the Firefox on Windows radio box.
1037 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
1038 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
1039 // Update the user agent menu item title.
1040 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
1042 // Select the Chrome on Windows radio box.
1043 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
1044 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
1045 // Update the user agent menu item title.
1046 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
1048 // Select the Edge on Windows radio box.
1049 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
1050 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
1051 // Update the user agent menu item title.
1052 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
1054 // Select the Internet on Windows radio box.
1055 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
1056 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
1057 // Update the user agent menu item title.
1058 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
1060 // Select the Safari on macOS radio box.
1061 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
1062 } else { // Custom user agent.
1063 // Update the user agent menu item title.
1064 userAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
1066 // Select the Custom radio box.
1067 menu.findItem(R.id.user_agent_custom).setChecked(true);
1070 // Set the font size title.
1071 fontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
1073 // Run all the other default commands.
1074 super.onPrepareOptionsMenu(menu);
1076 // Display the menu.
1081 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
1082 @SuppressLint("SetJavaScriptEnabled")
1083 public boolean onOptionsItemSelected(MenuItem menuItem) {
1084 // Get the selected menu item ID.
1085 int menuItemId = menuItem.getItemId();
1087 // Get a handle for the shared preferences.
1088 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1090 // Get a handle for the cookie manager.
1091 CookieManager cookieManager = CookieManager.getInstance();
1093 // Run the commands that correlate to the selected menu item.
1094 switch (menuItemId) {
1095 case R.id.toggle_javascript:
1096 // Toggle the JavaScript status.
1097 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1099 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1100 updatePrivacyIcons(true);
1102 // Display a `Snackbar`.
1103 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled.
1104 Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1105 } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled.
1106 Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1107 } else { // Privacy mode.
1108 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1111 // Reload the current WebView.
1112 currentWebView.reload();
1114 // Consume the event.
1118 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
1119 // Reload the current WebView.
1120 currentWebView.reload();
1121 } else { // The stop button was pushed.
1122 // Stop the loading of the WebView.
1123 currentWebView.stopLoading();
1126 // Consume the event.
1129 case R.id.bookmarks:
1130 // Open the bookmarks drawer.
1131 drawerLayout.openDrawer(GravityCompat.END);
1133 // Consume the event.
1136 case R.id.toggle_first_party_cookies:
1137 // Switch the first-party cookie status.
1138 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1140 // Store the first-party cookie status.
1141 currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
1143 // Update the menu checkbox.
1144 menuItem.setChecked(cookieManager.acceptCookie());
1146 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1147 updatePrivacyIcons(true);
1149 // Display a snackbar.
1150 if (cookieManager.acceptCookie()) { // First-party cookies are enabled.
1151 Snackbar.make(webViewPager, R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1152 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
1153 Snackbar.make(webViewPager, R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1154 } else { // Privacy mode.
1155 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1158 // Reload the current WebView.
1159 currentWebView.reload();
1161 // Consume the event.
1164 case R.id.toggle_third_party_cookies:
1165 if (Build.VERSION.SDK_INT >= 21) {
1166 // Switch the status of thirdPartyCookiesEnabled.
1167 cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
1169 // Update the menu checkbox.
1170 menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1172 // Display a snackbar.
1173 if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
1174 Snackbar.make(webViewPager, R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1176 Snackbar.make(webViewPager, R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1179 // Reload the current WebView.
1180 currentWebView.reload();
1181 } // Else do nothing because SDK < 21.
1183 // Consume the event.
1186 case R.id.toggle_dom_storage:
1187 // Toggle the status of domStorageEnabled.
1188 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1190 // Update the menu checkbox.
1191 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1193 // Update the privacy icon. `true` refreshes the app bar icons.
1194 updatePrivacyIcons(true);
1196 // Display a snackbar.
1197 if (currentWebView.getSettings().getDomStorageEnabled()) {
1198 Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1200 Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1203 // Reload the current WebView.
1204 currentWebView.reload();
1206 // Consume the event.
1209 // Form data can be removed once the minimum API >= 26.
1210 case R.id.toggle_save_form_data:
1211 // Switch the status of saveFormDataEnabled.
1212 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1214 // Update the menu checkbox.
1215 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1217 // Display a snackbar.
1218 if (currentWebView.getSettings().getSaveFormData()) {
1219 Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1221 Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1224 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1225 updatePrivacyIcons(true);
1227 // Reload the current WebView.
1228 currentWebView.reload();
1230 // Consume the event.
1233 case R.id.clear_cookies:
1234 Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1235 .setAction(R.string.undo, v -> {
1236 // Do nothing because everything will be handled by `onDismissed()` below.
1238 .addCallback(new Snackbar.Callback() {
1239 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1241 public void onDismissed(Snackbar snackbar, int event) {
1242 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1243 // Delete the cookies, which command varies by SDK.
1244 if (Build.VERSION.SDK_INT < 21) {
1245 cookieManager.removeAllCookie();
1247 cookieManager.removeAllCookies(null);
1254 // Consume the event.
1257 case R.id.clear_dom_storage:
1258 Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1259 .setAction(R.string.undo, v -> {
1260 // Do nothing because everything will be handled by `onDismissed()` below.
1262 .addCallback(new Snackbar.Callback() {
1263 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1265 public void onDismissed(Snackbar snackbar, int event) {
1266 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1267 // Delete the DOM Storage.
1268 WebStorage webStorage = WebStorage.getInstance();
1269 webStorage.deleteAllData();
1271 // Initialize a handler to manually delete the DOM storage files and directories.
1272 Handler deleteDomStorageHandler = new Handler();
1274 // Setup a runnable to manually delete the DOM storage files and directories.
1275 Runnable deleteDomStorageRunnable = () -> {
1277 // Get a handle for the runtime.
1278 Runtime runtime = Runtime.getRuntime();
1280 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1281 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1282 String privateDataDirectoryString = getApplicationInfo().dataDir;
1284 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1285 Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1287 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1288 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1289 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1290 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1291 Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1293 // Wait for the processes to finish.
1294 deleteLocalStorageProcess.waitFor();
1295 deleteIndexProcess.waitFor();
1296 deleteQuotaManagerProcess.waitFor();
1297 deleteQuotaManagerJournalProcess.waitFor();
1298 deleteDatabasesProcess.waitFor();
1299 } catch (Exception exception) {
1300 // Do nothing if an error is thrown.
1304 // Manually delete the DOM storage files after 200 milliseconds.
1305 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1311 // Consume the event.
1314 // Form data can be remove once the minimum API >= 26.
1315 case R.id.clear_form_data:
1316 Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1317 .setAction(R.string.undo, v -> {
1318 // Do nothing because everything will be handled by `onDismissed()` below.
1320 .addCallback(new Snackbar.Callback() {
1321 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1323 public void onDismissed(Snackbar snackbar, int event) {
1324 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1325 // Delete the form data.
1326 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1327 mainWebViewDatabase.clearFormData();
1333 // Consume the event.
1337 // Toggle the EasyList status.
1338 currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1340 // Update the menu checkbox.
1341 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1343 // Reload the current WebView.
1344 currentWebView.reload();
1346 // Consume the event.
1349 case R.id.easyprivacy:
1350 // Toggle the EasyPrivacy status.
1351 currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1353 // Update the menu checkbox.
1354 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1356 // Reload the current WebView.
1357 currentWebView.reload();
1359 // Consume the event.
1362 case R.id.fanboys_annoyance_list:
1363 // Toggle Fanboy's Annoyance List status.
1364 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1366 // Update the menu checkbox.
1367 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1369 // Update the staus of Fanboy's Social Blocking List.
1370 MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
1371 fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1373 // Reload the current WebView.
1374 currentWebView.reload();
1376 // Consume the event.
1379 case R.id.fanboys_social_blocking_list:
1380 // Toggle Fanboy's Social Blocking List status.
1381 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1383 // Update the menu checkbox.
1384 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1386 // Reload the current WebView.
1387 currentWebView.reload();
1389 // Consume the event.
1392 case R.id.ultralist:
1393 // Toggle the UltraList status.
1394 currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1396 // Update the menu checkbox.
1397 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1399 // Reload the current WebView.
1400 currentWebView.reload();
1402 // Consume the event.
1405 case R.id.ultraprivacy:
1406 // Toggle the UltraPrivacy status.
1407 currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1409 // Update the menu checkbox.
1410 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1412 // Reload the current WebView.
1413 currentWebView.reload();
1415 // Consume the event.
1418 case R.id.block_all_third_party_requests:
1419 //Toggle the third-party requests blocker status.
1420 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1422 // Update the menu checkbox.
1423 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1425 // Reload the current WebView.
1426 currentWebView.reload();
1428 // Consume the event.
1431 case R.id.proxy_none:
1432 // Update the proxy mode.
1433 proxyMode = ProxyHelper.NONE;
1435 // Apply the proxy mode.
1438 // Consume the event.
1441 case R.id.proxy_tor:
1442 // Update the proxy mode.
1443 proxyMode = ProxyHelper.TOR;
1445 // Apply the proxy mode.
1448 // Consume the event.
1451 case R.id.proxy_i2p:
1452 // Update the proxy mode.
1453 proxyMode = ProxyHelper.I2P;
1455 // Apply the proxy mode.
1458 // Consume the event.
1461 case R.id.proxy_custom:
1462 // Update the proxy mode.
1463 proxyMode = ProxyHelper.CUSTOM;
1465 // Apply the proxy mode.
1468 // Consume the event.
1471 case R.id.user_agent_privacy_browser:
1472 // Update the user agent.
1473 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1475 // Reload the current WebView.
1476 currentWebView.reload();
1478 // Consume the event.
1481 case R.id.user_agent_webview_default:
1482 // Update the user agent.
1483 currentWebView.getSettings().setUserAgentString("");
1485 // Reload the current WebView.
1486 currentWebView.reload();
1488 // Consume the event.
1491 case R.id.user_agent_firefox_on_android:
1492 // Update the user agent.
1493 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1495 // Reload the current WebView.
1496 currentWebView.reload();
1498 // Consume the event.
1501 case R.id.user_agent_chrome_on_android:
1502 // Update the user agent.
1503 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1505 // Reload the current WebView.
1506 currentWebView.reload();
1508 // Consume the event.
1511 case R.id.user_agent_safari_on_ios:
1512 // Update the user agent.
1513 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1515 // Reload the current WebView.
1516 currentWebView.reload();
1518 // Consume the event.
1521 case R.id.user_agent_firefox_on_linux:
1522 // Update the user agent.
1523 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1525 // Reload the current WebView.
1526 currentWebView.reload();
1528 // Consume the event.
1531 case R.id.user_agent_chromium_on_linux:
1532 // Update the user agent.
1533 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1535 // Reload the current WebView.
1536 currentWebView.reload();
1538 // Consume the event.
1541 case R.id.user_agent_firefox_on_windows:
1542 // Update the user agent.
1543 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1545 // Reload the current WebView.
1546 currentWebView.reload();
1548 // Consume the event.
1551 case R.id.user_agent_chrome_on_windows:
1552 // Update the user agent.
1553 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1555 // Reload the current WebView.
1556 currentWebView.reload();
1558 // Consume the event.
1561 case R.id.user_agent_edge_on_windows:
1562 // Update the user agent.
1563 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1565 // Reload the current WebView.
1566 currentWebView.reload();
1568 // Consume the event.
1571 case R.id.user_agent_internet_explorer_on_windows:
1572 // Update the user agent.
1573 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1575 // Reload the current WebView.
1576 currentWebView.reload();
1578 // Consume the event.
1581 case R.id.user_agent_safari_on_macos:
1582 // Update the user agent.
1583 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1585 // Reload the current WebView.
1586 currentWebView.reload();
1588 // Consume the event.
1591 case R.id.user_agent_custom:
1592 // Update the user agent.
1593 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1595 // Reload the current WebView.
1596 currentWebView.reload();
1598 // Consume the event.
1601 case R.id.font_size:
1602 // Instantiate the font size dialog.
1603 DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1605 // Show the font size dialog.
1606 fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1608 // Consume the event.
1611 case R.id.swipe_to_refresh:
1612 // Toggle the stored status of swipe to refresh.
1613 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1615 // Get a handle for the swipe refresh layout.
1616 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
1618 // Update the swipe refresh layout.
1619 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
1620 // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
1621 swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
1622 } else { // Swipe to refresh is disabled.
1623 // Disable the swipe refresh layout.
1624 swipeRefreshLayout.setEnabled(false);
1627 // Consume the event.
1630 case R.id.wide_viewport:
1631 // Toggle the viewport.
1632 currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1634 // Consume the event.
1637 case R.id.display_images:
1638 if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
1639 // Disable loading of images.
1640 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1642 // Reload the website to remove existing images.
1643 currentWebView.reload();
1644 } else { // Images are not currently loaded automatically.
1645 // Enable loading of images. Missing images will be loaded without the need for a reload.
1646 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1649 // Consume the event.
1652 case R.id.dark_webview:
1653 // Check to see if dark WebView is supported by this WebView.
1654 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
1655 // Toggle the dark WebView setting.
1656 if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled.
1657 // Turn off dark WebView.
1658 WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
1659 } else { // Dark WebView is currently disabled.
1660 // turn on dark WebView.
1661 WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
1665 // Consume the event.
1668 case R.id.find_on_page:
1669 // Get a handle for the views.
1670 Toolbar toolbar = findViewById(R.id.toolbar);
1671 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1672 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1674 // Set the minimum height of the find on page linear layout to match the toolbar.
1675 findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1677 // Hide the toolbar.
1678 toolbar.setVisibility(View.GONE);
1680 // Show the find on page linear layout.
1681 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1683 // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
1684 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1685 findOnPageEditText.postDelayed(() -> {
1686 // Set the focus on `findOnPageEditText`.
1687 findOnPageEditText.requestFocus();
1689 // Get a handle for the input method manager.
1690 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1692 // Remove the lint warning below that the input method manager might be null.
1693 assert inputMethodManager != null;
1695 // Display the keyboard. `0` sets no input flags.
1696 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1699 // Consume the event.
1703 // Get a print manager instance.
1704 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1706 // Remove the lint error below that print manager might be null.
1707 assert printManager != null;
1709 // Create a print document adapter from the current WebView.
1710 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1712 // Print the document.
1713 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1715 // Consume the event.
1719 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
1720 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
1721 currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
1723 // Consume the event.
1726 case R.id.save_as_archive:
1727 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
1728 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_ARCHIVE, currentWebView.getSettings().getUserAgentString(),
1729 currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
1731 // Consume the event.
1734 case R.id.save_as_image:
1735 // Prepare the save dialog. The dialog will be displayed once the file size adn the content disposition have been acquired.
1736 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_AS_IMAGE, currentWebView.getSettings().getUserAgentString(),
1737 currentWebView.getAcceptFirstPartyCookies()).execute(currentWebView.getCurrentUrl());
1739 // Consume the event.
1742 case R.id.add_to_homescreen:
1743 // Instantiate the create home screen shortcut dialog.
1744 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1745 currentWebView.getFavoriteOrDefaultIcon());
1747 // Show the create home screen shortcut dialog.
1748 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1750 // Consume the event.
1753 case R.id.view_source:
1754 // Create an intent to launch the view source activity.
1755 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1757 // Add the variables to the intent.
1758 viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
1759 viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
1762 startActivity(viewSourceIntent);
1764 // Consume the event.
1767 case R.id.share_url:
1768 // Setup the share string.
1769 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1771 // Create the share intent.
1772 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1773 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1774 shareIntent.setType("text/plain");
1777 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1779 // Consume the event.
1782 case R.id.open_with_app:
1783 // Open the URL with an outside app.
1784 openWithApp(currentWebView.getUrl());
1786 // Consume the event.
1789 case R.id.open_with_browser:
1790 // Open the URL with an outside browser.
1791 openWithBrowser(currentWebView.getUrl());
1793 // Consume the event.
1796 case R.id.add_or_edit_domain:
1797 if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings.
1798 // Reapply the domain settings on returning to `MainWebViewActivity`.
1799 reapplyDomainSettingsOnRestart = true;
1801 // Create an intent to launch the domains activity.
1802 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1804 // Add the extra information to the intent.
1805 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1806 domainsIntent.putExtra("close_on_back", true);
1807 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1809 // Get the current certificate.
1810 SslCertificate sslCertificate = currentWebView.getCertificate();
1812 // Check to see if the SSL certificate is populated.
1813 if (sslCertificate != null) {
1814 // Extract the certificate to strings.
1815 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1816 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1817 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1818 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1819 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1820 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1821 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1822 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1824 // Add the certificate to the intent.
1825 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1826 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1827 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1828 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1829 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1830 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1831 domainsIntent.putExtra("ssl_start_date", startDateLong);
1832 domainsIntent.putExtra("ssl_end_date", endDateLong);
1835 // Check to see if the current IP addresses have been received.
1836 if (currentWebView.hasCurrentIpAddresses()) {
1837 // Add the current IP addresses to the intent.
1838 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1842 startActivity(domainsIntent);
1843 } else { // Add a new domain.
1844 // Apply the new domain settings on returning to `MainWebViewActivity`.
1845 reapplyDomainSettingsOnRestart = true;
1847 // Get the current domain
1848 Uri currentUri = Uri.parse(currentWebView.getUrl());
1849 String currentDomain = currentUri.getHost();
1851 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1852 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1854 // Create the domain and store the database ID.
1855 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1857 // Create an intent to launch the domains activity.
1858 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1860 // Add the extra information to the intent.
1861 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1862 domainsIntent.putExtra("close_on_back", true);
1863 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1865 // Get the current certificate.
1866 SslCertificate sslCertificate = currentWebView.getCertificate();
1868 // Check to see if the SSL certificate is populated.
1869 if (sslCertificate != null) {
1870 // Extract the certificate to strings.
1871 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1872 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1873 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1874 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1875 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1876 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1877 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1878 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1880 // Add the certificate to the intent.
1881 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1882 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1883 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1884 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1885 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1886 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1887 domainsIntent.putExtra("ssl_start_date", startDateLong);
1888 domainsIntent.putExtra("ssl_end_date", endDateLong);
1891 // Check to see if the current IP addresses have been received.
1892 if (currentWebView.hasCurrentIpAddresses()) {
1893 // Add the current IP addresses to the intent.
1894 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1898 startActivity(domainsIntent);
1901 // Consume the event.
1904 case R.id.ad_consent:
1905 // Instantiate the ad consent dialog.
1906 DialogFragment adConsentDialogFragment = new AdConsentDialog();
1908 // Display the ad consent dialog.
1909 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1911 // Consume the event.
1915 // Don't consume the event.
1916 return super.onOptionsItemSelected(menuItem);
1920 // removeAllCookies is deprecated, but it is required for API < 21.
1922 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1923 // Get the menu item ID.
1924 int menuItemId = menuItem.getItemId();
1926 // Get a handle for the shared preferences.
1927 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1929 // Run the commands that correspond to the selected menu item.
1930 switch (menuItemId) {
1931 case R.id.clear_and_exit:
1932 // Clear and exit Privacy Browser.
1937 // Load the homepage.
1938 loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1942 if (currentWebView.canGoBack()) {
1943 // Get the current web back forward list.
1944 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1946 // Get the previous entry URL.
1947 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
1949 // Apply the domain settings.
1950 applyDomainSettings(currentWebView, previousUrl, false, false);
1952 // Load the previous website in the history.
1953 currentWebView.goBack();
1958 if (currentWebView.canGoForward()) {
1959 // Get the current web back forward list.
1960 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1962 // Get the next entry URL.
1963 String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
1965 // Apply the domain settings.
1966 applyDomainSettings(currentWebView, nextUrl, false, false);
1968 // Load the next website in the history.
1969 currentWebView.goForward();
1974 // Instantiate the URL history dialog.
1975 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
1977 // Show the URL history dialog.
1978 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
1982 // Instantiate the open file dialog.
1983 DialogFragment openDialogFragment = new OpenDialog();
1985 // Show the open file dialog.
1986 openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
1990 // Populate the resource requests.
1991 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
1993 // Create an intent to launch the Requests activity.
1994 Intent requestsIntent = new Intent(this, RequestsActivity.class);
1996 // Add the block third-party requests status to the intent.
1997 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
2000 startActivity(requestsIntent);
2003 case R.id.downloads:
2004 // Launch the system Download Manager.
2005 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2007 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2008 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2011 startActivity(downloadManagerIntent);
2015 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2016 reapplyDomainSettingsOnRestart = true;
2018 // Launch the domains activity.
2019 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2021 // Add the extra information to the intent.
2022 domainsIntent.putExtra("current_url", currentWebView.getUrl());
2024 // Get the current certificate.
2025 SslCertificate sslCertificate = currentWebView.getCertificate();
2027 // Check to see if the SSL certificate is populated.
2028 if (sslCertificate != null) {
2029 // Extract the certificate to strings.
2030 String issuedToCName = sslCertificate.getIssuedTo().getCName();
2031 String issuedToOName = sslCertificate.getIssuedTo().getOName();
2032 String issuedToUName = sslCertificate.getIssuedTo().getUName();
2033 String issuedByCName = sslCertificate.getIssuedBy().getCName();
2034 String issuedByOName = sslCertificate.getIssuedBy().getOName();
2035 String issuedByUName = sslCertificate.getIssuedBy().getUName();
2036 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2037 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2039 // Add the certificate to the intent.
2040 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2041 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2042 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2043 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2044 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2045 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2046 domainsIntent.putExtra("ssl_start_date", startDateLong);
2047 domainsIntent.putExtra("ssl_end_date", endDateLong);
2050 // Check to see if the current IP addresses have been received.
2051 if (currentWebView.hasCurrentIpAddresses()) {
2052 // Add the current IP addresses to the intent.
2053 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2057 startActivity(domainsIntent);
2061 // Set the flag to reapply app settings on restart when returning from Settings.
2062 reapplyAppSettingsOnRestart = true;
2064 // Set the flag to reapply the domain settings on restart when returning from Settings.
2065 reapplyDomainSettingsOnRestart = true;
2067 // Launch the settings activity.
2068 Intent settingsIntent = new Intent(this, SettingsActivity.class);
2069 startActivity(settingsIntent);
2072 case R.id.import_export:
2073 // Launch the import/export activity.
2074 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
2075 startActivity(importExportIntent);
2079 // Launch the logcat activity.
2080 Intent logcatIntent = new Intent(this, LogcatActivity.class);
2081 startActivity(logcatIntent);
2085 // Launch `GuideActivity`.
2086 Intent guideIntent = new Intent(this, GuideActivity.class);
2087 startActivity(guideIntent);
2091 // Create an intent to launch the about activity.
2092 Intent aboutIntent = new Intent(this, AboutActivity.class);
2094 // Create a string array for the blocklist versions.
2095 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],
2096 ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
2098 // Add the blocklist versions to the intent.
2099 aboutIntent.putExtra("blocklist_versions", blocklistVersions);
2102 startActivity(aboutIntent);
2106 // Close the navigation drawer.
2107 drawerLayout.closeDrawer(GravityCompat.START);
2112 public void onPostCreate(Bundle savedInstanceState) {
2113 // Run the default commands.
2114 super.onPostCreate(savedInstanceState);
2116 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
2117 actionBarDrawerToggle.syncState();
2121 public void onConfigurationChanged(@NonNull Configuration newConfig) {
2122 // Run the default commands.
2123 super.onConfigurationChanged(newConfig);
2125 // Reload the ad for the free flavor if not in full screen mode.
2126 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2127 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2128 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2131 // `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:
2132 // https://code.google.com/p/android/issues/detail?id=20493#c8
2133 // ActivityCompat.invalidateOptionsMenu(this);
2137 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2138 // Store the hit test result.
2139 final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2141 // Define the URL strings.
2142 final String imageUrl;
2143 final String linkUrl;
2145 // Get handles for the system managers.
2146 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2148 // Remove the lint errors below that the clipboard manager might be null.
2149 assert clipboardManager != null;
2151 // Process the link according to the type.
2152 switch (hitTestResult.getType()) {
2153 // `SRC_ANCHOR_TYPE` is a link.
2154 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2155 // Get the target URL.
2156 linkUrl = hitTestResult.getExtra();
2158 // Set the target URL as the title of the `ContextMenu`.
2159 menu.setHeaderTitle(linkUrl);
2161 // Add an Open in New Tab entry.
2162 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2163 // Load the link URL in a new tab and move to it.
2164 addNewTab(linkUrl, true);
2166 // Consume the event.
2170 // Add an Open in Background entry.
2171 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2172 // Load the link URL in a new tab but do not move to it.
2173 addNewTab(linkUrl, false);
2175 // Consume the event.
2179 // Add an Open with App entry.
2180 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2181 openWithApp(linkUrl);
2183 // Consume the event.
2187 // Add an Open with Browser entry.
2188 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2189 openWithBrowser(linkUrl);
2191 // Consume the event.
2195 // Add a Copy URL entry.
2196 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2197 // Save the link URL in a `ClipData`.
2198 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2200 // Set the `ClipData` as the clipboard's primary clip.
2201 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2203 // Consume the event.
2207 // Add a Save URL entry.
2208 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2209 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2210 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2211 currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
2213 // Consume the event.
2217 // Add an empty Cancel entry, which by default closes the context menu.
2218 menu.add(R.string.cancel);
2221 // `IMAGE_TYPE` is an image.
2222 case WebView.HitTestResult.IMAGE_TYPE:
2223 // Get the image URL.
2224 imageUrl = hitTestResult.getExtra();
2226 // Set the image URL as the title of the context menu.
2227 menu.setHeaderTitle(imageUrl);
2229 // Add an Open in New Tab entry.
2230 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2231 // Load the image in a new tab.
2232 addNewTab(imageUrl, true);
2234 // Consume the event.
2238 // Add an Open with App entry.
2239 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2240 // Open the image URL with an external app.
2241 openWithApp(imageUrl);
2243 // Consume the event.
2247 // Add an Open with Browser entry.
2248 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2249 // Open the image URL with an external browser.
2250 openWithBrowser(imageUrl);
2252 // Consume the event.
2256 // Add a View Image entry.
2257 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2258 // Load the image in the current tab.
2259 loadUrl(currentWebView, imageUrl);
2261 // Consume the event.
2265 // Add a Save Image entry.
2266 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2267 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2268 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2269 currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
2271 // Consume the event.
2275 // Add a Copy URL entry.
2276 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2277 // Save the image URL in a clip data.
2278 ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2280 // Set the clip data as the clipboard's primary clip.
2281 clipboardManager.setPrimaryClip(imageTypeClipData);
2283 // Consume the event.
2287 // Add an empty Cancel entry, which by default closes the context menu.
2288 menu.add(R.string.cancel);
2291 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2292 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2293 // Get the image URL.
2294 imageUrl = hitTestResult.getExtra();
2296 // Instantiate a handler.
2297 Handler handler = new Handler();
2299 // Get a message from the handler.
2300 Message message = handler.obtainMessage();
2302 // Request the image details from the last touched node be returned in the message.
2303 currentWebView.requestFocusNodeHref(message);
2305 // Get the link URL from the message data.
2306 linkUrl = message.getData().getString("url");
2308 // Set the link URL as the title of the context menu.
2309 menu.setHeaderTitle(linkUrl);
2311 // Add an Open in New Tab entry.
2312 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2313 // Load the link URL in a new tab and move to it.
2314 addNewTab(linkUrl, true);
2316 // Consume the event.
2320 // Add an Open in Background entry.
2321 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2322 // Lod the link URL in a new tab but do not move to it.
2323 addNewTab(linkUrl, false);
2325 // Consume the event.
2329 // Add an Open Image in New Tab entry.
2330 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2331 // Load the image in a new tab and move to it.
2332 addNewTab(imageUrl, true);
2334 // Consume the event.
2338 // Add an Open with App entry.
2339 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2340 // Open the link URL with an external app.
2341 openWithApp(linkUrl);
2343 // Consume the event.
2347 // Add an Open with Browser entry.
2348 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2349 // Open the link URL with an external browser.
2350 openWithBrowser(linkUrl);
2352 // Consume the event.
2356 // Add a View Image entry.
2357 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2358 // View the image in the current tab.
2359 loadUrl(currentWebView, imageUrl);
2361 // Consume the event.
2365 // Add a Save Image entry.
2366 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2367 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2368 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2369 currentWebView.getAcceptFirstPartyCookies()).execute(imageUrl);
2371 // Consume the event.
2375 // Add a Copy URL entry.
2376 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2377 // Save the link URL in a clip data.
2378 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2380 // Set the clip data as the clipboard's primary clip.
2381 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2383 // Consume the event.
2387 // Add a Save URL entry.
2388 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2389 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2390 new PrepareSaveDialog(this, this, getSupportFragmentManager(), StoragePermissionDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2391 currentWebView.getAcceptFirstPartyCookies()).execute(linkUrl);
2393 // Consume the event.
2397 // Add an empty Cancel entry, which by default closes the context menu.
2398 menu.add(R.string.cancel);
2401 case WebView.HitTestResult.EMAIL_TYPE:
2402 // Get the target URL.
2403 linkUrl = hitTestResult.getExtra();
2405 // Set the target URL as the title of the `ContextMenu`.
2406 menu.setHeaderTitle(linkUrl);
2408 // Add a Write Email entry.
2409 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2410 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2411 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2413 // Parse the url and set it as the data for the `Intent`.
2414 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2416 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2417 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2420 startActivity(emailIntent);
2422 // Consume the event.
2426 // Add a Copy Email Address entry.
2427 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2428 // Save the email address in a `ClipData`.
2429 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2431 // Set the `ClipData` as the clipboard's primary clip.
2432 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2434 // Consume the event.
2438 // Add an empty Cancel entry, which by default closes the context menu.
2439 menu.add(R.string.cancel);
2445 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2446 // Get a handle for the bookmarks list view.
2447 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2450 Dialog dialog = dialogFragment.getDialog();
2452 // Remove the incorrect lint warning below that the dialog might be null.
2453 assert dialog != null;
2455 // Get the views from the dialog fragment.
2456 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2457 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2459 // Extract the strings from the edit texts.
2460 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2461 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2463 // Create a favorite icon byte array output stream.
2464 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2466 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2467 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2469 // Convert the favorite icon byte array stream to a byte array.
2470 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2472 // Display the new bookmark below the current items in the (0 indexed) list.
2473 int newBookmarkDisplayOrder = bookmarksListView.getCount();
2475 // Create the bookmark.
2476 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2478 // Update the bookmarks cursor with the current contents of this folder.
2479 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2481 // Update the list view.
2482 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2484 // Scroll to the new bookmark.
2485 bookmarksListView.setSelection(newBookmarkDisplayOrder);
2489 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
2490 // Get a handle for the bookmarks list view.
2491 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2494 Dialog dialog = dialogFragment.getDialog();
2496 // Remove the incorrect lint warning below that the dialog might be null.
2497 assert dialog != null;
2499 // Get handles for the views in the dialog fragment.
2500 EditText createFolderNameEditText = dialog.findViewById(R.id.create_folder_name_edittext);
2501 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.create_folder_default_icon_radiobutton);
2502 ImageView folderIconImageView = dialog.findViewById(R.id.create_folder_default_icon);
2504 // Get new folder name string.
2505 String folderNameString = createFolderNameEditText.getText().toString();
2507 // Create a folder icon bitmap.
2508 Bitmap folderIconBitmap;
2510 // Set the folder icon bitmap according to the dialog.
2511 if (defaultFolderIconRadioButton.isChecked()) { // Use the default folder icon.
2512 // Get the default folder icon drawable.
2513 Drawable folderIconDrawable = folderIconImageView.getDrawable();
2515 // Convert the folder icon drawable to a bitmap drawable.
2516 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2518 // Convert the folder icon bitmap drawable to a bitmap.
2519 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2520 } else { // Use the WebView favorite icon.
2521 // Copy the favorite icon bitmap to the folder icon bitmap.
2522 folderIconBitmap = favoriteIconBitmap;
2525 // Create a folder icon byte array output stream.
2526 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2528 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2529 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2531 // Convert the folder icon byte array stream to a byte array.
2532 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2534 // Move all the bookmarks down one in the display order.
2535 for (int i = 0; i < bookmarksListView.getCount(); i++) {
2536 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2537 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2540 // Create the folder, which will be placed at the top of the `ListView`.
2541 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);
2543 // Update the bookmarks cursor with the current contents of this folder.
2544 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2546 // Update the `ListView`.
2547 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2549 // Scroll to the new folder.
2550 bookmarksListView.setSelection(0);
2554 public void onSaveBookmarkFolder(DialogFragment dialogFragment, int selectedFolderDatabaseId, Bitmap favoriteIconBitmap) {
2556 Dialog dialog = dialogFragment.getDialog();
2558 // Remove the incorrect lint warning below that the dialog might be null.
2559 assert dialog != null;
2561 // Get handles for the views from `dialogFragment`.
2562 EditText editFolderNameEditText = dialog.findViewById(R.id.edit_folder_name_edittext);
2563 RadioButton currentFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_current_icon_radiobutton);
2564 RadioButton defaultFolderIconRadioButton = dialog.findViewById(R.id.edit_folder_default_icon_radiobutton);
2565 ImageView defaultFolderIconImageView = dialog.findViewById(R.id.edit_folder_default_icon_imageview);
2567 // Get the new folder name.
2568 String newFolderNameString = editFolderNameEditText.getText().toString();
2570 // Check if the favorite icon has changed.
2571 if (currentFolderIconRadioButton.isChecked()) { // Only the name has changed.
2572 // Update the name in the database.
2573 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString);
2574 } else if (!currentFolderIconRadioButton.isChecked() && newFolderNameString.equals(oldFolderNameString)) { // Only the icon has changed.
2575 // Create the new folder icon Bitmap.
2576 Bitmap folderIconBitmap;
2578 // Populate the new folder icon bitmap.
2579 if (defaultFolderIconRadioButton.isChecked()) {
2580 // Get the default folder icon drawable.
2581 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2583 // Convert the folder icon drawable to a bitmap drawable.
2584 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2586 // Convert the folder icon bitmap drawable to a bitmap.
2587 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2588 } else { // Use the `WebView` favorite icon.
2589 // Copy the favorite icon bitmap to the folder icon bitmap.
2590 folderIconBitmap = favoriteIconBitmap;
2593 // Create a folder icon byte array output stream.
2594 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2596 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2597 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2599 // Convert the folder icon byte array stream to a byte array.
2600 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2602 // Update the folder icon in the database.
2603 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, newFolderIconByteArray);
2604 } else { // The folder icon and the name have changed.
2605 // Get the new folder icon `Bitmap`.
2606 Bitmap folderIconBitmap;
2607 if (defaultFolderIconRadioButton.isChecked()) {
2608 // Get the default folder icon drawable.
2609 Drawable folderIconDrawable = defaultFolderIconImageView.getDrawable();
2611 // Convert the folder icon drawable to a bitmap drawable.
2612 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2614 // Convert the folder icon bitmap drawable to a bitmap.
2615 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2616 } else { // Use the `WebView` favorite icon.
2617 // Copy the favorite icon bitmap to the folder icon bitmap.
2618 folderIconBitmap = favoriteIconBitmap;
2621 // Create a folder icon byte array output stream.
2622 ByteArrayOutputStream newFolderIconByteArrayOutputStream = new ByteArrayOutputStream();
2624 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2625 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, newFolderIconByteArrayOutputStream);
2627 // Convert the folder icon byte array stream to a byte array.
2628 byte[] newFolderIconByteArray = newFolderIconByteArrayOutputStream.toByteArray();
2630 // Update the folder name and icon in the database.
2631 bookmarksDatabaseHelper.updateFolder(selectedFolderDatabaseId, oldFolderNameString, newFolderNameString, newFolderIconByteArray);
2634 // Update the bookmarks cursor with the current contents of this folder.
2635 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2637 // Update the `ListView`.
2638 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2641 // Override `onBackPressed()` to handle the navigation drawer and and the WebViews.
2643 public void onBackPressed() {
2644 // Check the different options for processing `back`.
2645 if (drawerLayout.isDrawerVisible(GravityCompat.START)) { // The navigation drawer is open.
2646 // Close the navigation drawer.
2647 drawerLayout.closeDrawer(GravityCompat.START);
2648 } else if (drawerLayout.isDrawerVisible(GravityCompat.END)){ // The bookmarks drawer is open.
2649 // close the bookmarks drawer.
2650 drawerLayout.closeDrawer(GravityCompat.END);
2651 } else if (displayingFullScreenVideo) { // A full screen video is shown.
2652 // Get a handle for the layouts.
2653 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
2654 RelativeLayout mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
2655 FrameLayout fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
2657 // Re-enable the screen timeout.
2658 fullScreenVideoFrameLayout.setKeepScreenOn(false);
2660 // Unset the full screen video flag.
2661 displayingFullScreenVideo = false;
2663 // Remove all the views from the full screen video frame layout.
2664 fullScreenVideoFrameLayout.removeAllViews();
2666 // Hide the full screen video frame layout.