2 * Copyright © 2015-2019 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.Manifest;
25 import android.annotation.SuppressLint;
26 import android.app.Activity;
27 import android.app.DownloadManager;
28 import android.app.SearchManager;
29 import android.content.ActivityNotFoundException;
30 import android.content.BroadcastReceiver;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.SharedPreferences;
37 import android.content.pm.PackageManager;
38 import android.content.res.Configuration;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.Typeface;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.graphics.drawable.Drawable;
45 import android.net.Uri;
46 import android.net.http.SslCertificate;
47 import android.net.http.SslError;
48 import android.os.Build;
49 import android.os.Bundle;
50 import android.os.Environment;
51 import android.os.Handler;
52 import android.preference.PreferenceManager;
53 import android.print.PrintDocumentAdapter;
54 import android.print.PrintManager;
55 import android.text.Editable;
56 import android.text.Spanned;
57 import android.text.TextWatcher;
58 import android.text.style.ForegroundColorSpan;
59 import android.util.Patterns;
60 import android.view.ContextMenu;
61 import android.view.GestureDetector;
62 import android.view.KeyEvent;
63 import android.view.Menu;
64 import android.view.MenuItem;
65 import android.view.MotionEvent;
66 import android.view.View;
67 import android.view.ViewGroup;
68 import android.view.WindowManager;
69 import android.view.inputmethod.InputMethodManager;
70 import android.webkit.CookieManager;
71 import android.webkit.HttpAuthHandler;
72 import android.webkit.SslErrorHandler;
73 import android.webkit.ValueCallback;
74 import android.webkit.WebChromeClient;
75 import android.webkit.WebResourceResponse;
76 import android.webkit.WebSettings;
77 import android.webkit.WebStorage;
78 import android.webkit.WebView;
79 import android.webkit.WebViewClient;
80 import android.webkit.WebViewDatabase;
81 import android.widget.ArrayAdapter;
82 import android.widget.CursorAdapter;
83 import android.widget.EditText;
84 import android.widget.FrameLayout;
85 import android.widget.ImageView;
86 import android.widget.LinearLayout;
87 import android.widget.ListView;
88 import android.widget.ProgressBar;
89 import android.widget.RadioButton;
90 import android.widget.RelativeLayout;
91 import android.widget.TextView;
93 import androidx.annotation.NonNull;
94 import androidx.appcompat.app.ActionBar;
95 import androidx.appcompat.app.ActionBarDrawerToggle;
96 import androidx.appcompat.app.AppCompatActivity;
97 import androidx.appcompat.widget.Toolbar;
98 import androidx.core.app.ActivityCompat;
99 import androidx.core.content.ContextCompat;
100 import androidx.core.view.GravityCompat;
101 import androidx.drawerlayout.widget.DrawerLayout;
102 import androidx.fragment.app.DialogFragment;
103 import androidx.fragment.app.FragmentManager;
104 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
105 import androidx.viewpager.widget.ViewPager;
107 import com.google.android.material.floatingactionbutton.FloatingActionButton;
108 import com.google.android.material.navigation.NavigationView;
109 import com.google.android.material.snackbar.Snackbar;
110 import com.google.android.material.tabs.TabLayout;
112 import com.stoutner.privacybrowser.BuildConfig;
113 import com.stoutner.privacybrowser.R;
114 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
115 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
116 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
117 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
118 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
119 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
120 import com.stoutner.privacybrowser.dialogs.DownloadFileDialog;
121 import com.stoutner.privacybrowser.dialogs.DownloadImageDialog;
122 import com.stoutner.privacybrowser.dialogs.DownloadLocationPermissionDialog;
123 import com.stoutner.privacybrowser.dialogs.EditBookmarkDialog;
124 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
125 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
126 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
127 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
128 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
129 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
130 import com.stoutner.privacybrowser.helpers.AdHelper;
131 import com.stoutner.privacybrowser.helpers.BlockListHelper;
132 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
133 import com.stoutner.privacybrowser.helpers.CheckPinnedMismatchHelper;
134 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
135 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
136 import com.stoutner.privacybrowser.views.NestedScrollWebView;
138 import java.io.ByteArrayInputStream;
139 import java.io.ByteArrayOutputStream;
141 import java.io.IOException;
142 import java.io.UnsupportedEncodingException;
143 import java.net.MalformedURLException;
145 import java.net.URLDecoder;
146 import java.net.URLEncoder;
147 import java.util.ArrayList;
148 import java.util.Date;
149 import java.util.HashMap;
150 import java.util.HashSet;
151 import java.util.List;
152 import java.util.Map;
153 import java.util.Set;
155 // TODO. New tabs are white in dark mode.
156 // TODO. Find on page.
158 // AppCompatActivity from android.support.v7.app.AppCompatActivity must be used to have access to the SupportActionBar until the minimum API is >= 21.
159 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
160 DownloadFileDialog.DownloadFileListener, DownloadImageDialog.DownloadImageListener, DownloadLocationPermissionDialog.DownloadLocationPermissionDialogListener, EditBookmarkDialog.EditBookmarkListener,
161 EditBookmarkFolderDialog.EditBookmarkFolderListener, NavigationView.OnNavigationItemSelectedListener, WebViewTabFragment.NewTabListener {
163 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
164 public static String orbotStatus;
166 // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`.
167 public static WebViewPagerAdapter webViewPagerAdapter;
169 // The load URL on restart variables are public static so they can be accessed from `BookmarksActivity`. They are used in `onRestart()`.
170 public static boolean loadUrlOnRestart;
171 public static String urlToLoadOnRestart;
173 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
174 public static boolean restartFromBookmarksActivity;
176 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
177 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
178 public static String currentBookmarksFolder;
180 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
181 public final static int UNRECOGNIZED_USER_AGENT = -1;
182 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
183 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
184 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
185 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
186 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
190 // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
191 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxyThroughOrbot()`, and `applyDomainSettings()`.
192 private NestedScrollWebView currentWebView;
194 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
195 private final Map<String, String> customHeaders = new HashMap<>();
197 // The search URL is set in `applyProxyThroughOrbot()` and used in `onCreate()`, `onNewIntent()`, `loadURLFromTextBox()`, and `initializeWebView()`.
198 private String searchURL;
200 // The options menu is set in `onCreateOptionsMenu()` and used in `onOptionsItemSelected()`, `updatePrivacyIcons()`, and `initializeWebView()`.
201 private Menu optionsMenu;
203 // The blocklists are populated in `onCreate()` and accessed from `initializeWebView()`.
204 private ArrayList<List<String[]>> easyList;
205 private ArrayList<List<String[]>> easyPrivacy;
206 private ArrayList<List<String[]>> fanboysAnnoyanceList;
207 private ArrayList<List<String[]>> fanboysSocialList;
208 private ArrayList<List<String[]>> ultraPrivacy;
210 // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`.
211 private String webViewDefaultUserAgent;
213 // `proxyThroughOrbot` is used in `onRestart()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxyThroughOrbot()`.
214 private boolean proxyThroughOrbot;
216 // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`.
217 private boolean incognitoModeEnabled;
219 // The full screen browsing mode tracker is set it `applyAppSettings()` and used in `initializeWebView()`.
220 private boolean fullScreenBrowsingModeEnabled;
222 // `inFullScreenBrowsingMode` is used in `onCreate()`, `onConfigurationChanged()`, and `applyAppSettings()`.
223 private boolean inFullScreenBrowsingMode;
225 // The hide app bar tracker is used in `applyAppSettings()` and `initializeWebView()`.
226 private boolean hideAppBar;
228 // `reapplyDomainSettingsOnRestart` is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `onAddDomain()`, .
229 private boolean reapplyDomainSettingsOnRestart;
231 // `reapplyAppSettingsOnRestart` is used in `onNavigationItemSelected()` and `onRestart()`.
232 private boolean reapplyAppSettingsOnRestart;
234 // `displayingFullScreenVideo` is used in `onCreate()` and `onResume()`.
235 private boolean displayingFullScreenVideo;
237 // `orbotStatusBroadcastReceiver` is used in `onCreate()` and `onDestroy()`.
238 private BroadcastReceiver orbotStatusBroadcastReceiver;
240 // `waitingForOrbot` is used in `onCreate()`, `onResume()`, and `applyProxyThroughOrbot()`.
241 private boolean waitingForOrbot;
243 // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
244 private ActionBarDrawerToggle actionBarDrawerToggle;
246 // The color spans are used in `onCreate()` and `highlightUrlText()`.
247 private ForegroundColorSpan redColorSpan;
248 private ForegroundColorSpan initialGrayColorSpan;
249 private ForegroundColorSpan finalGrayColorSpan;
251 // The drawer header padding variables are used in `onCreate()` and `onConfigurationChanged()`.
252 private int drawerHeaderPaddingLeftAndRight;
253 private int drawerHeaderPaddingTop;
254 private int drawerHeaderPaddingBottom;
256 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
257 // and `loadBookmarksFolder()`.
258 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
260 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
261 private Cursor bookmarksCursor;
263 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
264 private CursorAdapter bookmarksCursorAdapter;
266 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
267 private String oldFolderNameString;
269 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
270 private ValueCallback<Uri[]> fileChooserCallback;
272 // The download strings are used in `onCreate()`, `onRequestPermissionResult()` and `initializeWebView()`.
273 private String downloadUrl;
274 private String downloadContentDisposition;
275 private long downloadContentLength;
277 // `downloadImageUrl` is used in `onCreateContextMenu()` and `onRequestPermissionResult()`.
278 private String downloadImageUrl;
280 // The request codes are used in `onCreate()`, `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, and `initializeWebView()`.
281 private final int DOWNLOAD_FILE_REQUEST_CODE = 1;
282 private final int DOWNLOAD_IMAGE_REQUEST_CODE = 2;
285 // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
286 @SuppressLint("ClickableViewAccessibility")
287 protected void onCreate(Bundle savedInstanceState) {
288 // Get a handle for the shared preferences.
289 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
291 // Get the theme and screenshot preferences.
292 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
293 boolean allowScreenshots = sharedPreferences.getBoolean("allow_screenshots", false);
295 // Disable screenshots if not allowed.
296 if (!allowScreenshots) {
297 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
300 // Set the activity theme.
302 setTheme(R.style.PrivacyBrowserDark);
304 setTheme(R.style.PrivacyBrowserLight);
307 // Run the default commands.
308 super.onCreate(savedInstanceState);
310 // Set the content view.
311 setContentView(R.layout.main_framelayout);
313 // Get a handle for the input method.
314 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
316 // Remove the lint warning below that the input method manager might be null.
317 assert inputMethodManager != null;
319 // Get a handle for the toolbar.
320 Toolbar toolbar = findViewById(R.id.toolbar);
322 // Set the action bar. `SupportActionBar` must be used until the minimum API is >= 21.
323 setSupportActionBar(toolbar);
325 // Get a handle for the action bar.
326 ActionBar actionBar = getSupportActionBar();
328 // This is needed to get rid of the Android Studio warning that the action bar might be null.
329 assert actionBar != null;
331 // Add the custom layout, which shows the URL text bar.
332 actionBar.setCustomView(R.layout.url_app_bar);
333 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
335 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
336 redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
337 initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
338 finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
340 // Get handles for the URL views.
341 EditText urlEditText = findViewById(R.id.url_edittext);
343 // Remove the formatting from `urlTextBar` when the user is editing the text.
344 urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
345 if (hasFocus) { // The user is editing the URL text box.
346 // Remove the highlighting.
347 urlEditText.getText().removeSpan(redColorSpan);
348 urlEditText.getText().removeSpan(initialGrayColorSpan);
349 urlEditText.getText().removeSpan(finalGrayColorSpan);
350 } else { // The user has stopped editing the URL text box.
351 // Move to the beginning of the string.
352 urlEditText.setSelection(0);
354 // Reapply the highlighting.
359 // Set the go button on the keyboard to load the URL in `urlTextBox`.
360 urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
361 // If the event is a key-down event on the `enter` button, load the URL.
362 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
363 // Load the URL into the mainWebView and consume the event.
364 loadUrlFromTextBox();
366 // If the enter key was pressed, consume the event.
369 // If any other key was pressed, do not consume the event.
374 // Initialize the Orbot status and the waiting for Orbot trackers.
375 orbotStatus = "unknown";
376 waitingForOrbot = false;
378 // Create an Orbot status `BroadcastReceiver`.
379 orbotStatusBroadcastReceiver = new BroadcastReceiver() {
381 public void onReceive(Context context, Intent intent) {
382 // Store the content of the status message in `orbotStatus`.
383 orbotStatus = intent.getStringExtra("org.torproject.android.intent.extra.STATUS");
385 // If Privacy Browser is waiting on Orbot, load the website now that Orbot is connected.
386 if (orbotStatus.equals("ON") && waitingForOrbot) {
387 // Reset the waiting for Orbot status.
388 waitingForOrbot = false;
390 // Get the intent that started the app.
391 Intent launchingIntent = getIntent();
393 // Get the information from the intent.
394 String launchingIntentAction = launchingIntent.getAction();
395 Uri launchingIntentUriData = launchingIntent.getData();
397 // If the intent action is a web search, perform the search.
398 if ((launchingIntentAction != null) && launchingIntentAction.equals(Intent.ACTION_WEB_SEARCH)) {
399 // Create an encoded URL string.
400 String encodedUrlString;
402 // Sanitize the search input and convert it to a search.
404 encodedUrlString = URLEncoder.encode(launchingIntent.getStringExtra(SearchManager.QUERY), "UTF-8");
405 } catch (UnsupportedEncodingException exception) {
406 encodedUrlString = "";
409 // Load the completed search URL.
410 loadUrl(searchURL + encodedUrlString);
411 } else if (launchingIntentUriData != null){ // Check to see if the intent contains a new URL.
412 // Load the URL from the intent.
413 loadUrl(launchingIntentUriData.toString());
414 } else { // The is no URL in the intent.
415 // Select the homepage based on the proxy through Orbot status.
416 if (proxyThroughOrbot) {
417 // Load the Tor homepage.
418 loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
420 // Load the normal homepage.
421 loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
428 // Register `orbotStatusBroadcastReceiver` on `this` context.
429 this.registerReceiver(orbotStatusBroadcastReceiver, new IntentFilter("org.torproject.android.intent.action.STATUS"));
431 // Instantiate the blocklist helper.
432 BlockListHelper blockListHelper = new BlockListHelper();
434 // Parse the block lists.
435 easyList = blockListHelper.parseBlockList(getAssets(), "blocklists/easylist.txt");
436 easyPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/easyprivacy.txt");
437 fanboysAnnoyanceList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-annoyance.txt");
438 fanboysSocialList = blockListHelper.parseBlockList(getAssets(), "blocklists/fanboy-social.txt");
439 ultraPrivacy = blockListHelper.parseBlockList(getAssets(), "blocklists/ultraprivacy.txt");
441 // Get handles for views that need to be modified.
442 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
443 NavigationView navigationView = findViewById(R.id.navigationview);
444 TabLayout tabLayout = findViewById(R.id.tablayout);
445 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
446 ViewPager webViewPager = findViewById(R.id.webviewpager);
447 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
448 FloatingActionButton launchBookmarksActivityFab = findViewById(R.id.launch_bookmarks_activity_fab);
449 FloatingActionButton createBookmarkFolderFab = findViewById(R.id.create_bookmark_folder_fab);
450 FloatingActionButton createBookmarkFab = findViewById(R.id.create_bookmark_fab);
451 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
453 // Listen for touches on the navigation menu.
454 navigationView.setNavigationItemSelectedListener(this);
456 // Get handles for the navigation menu and the back and forward menu items. The menu is zero-based.
457 Menu navigationMenu = navigationView.getMenu();
458 MenuItem navigationCloseTabMenuItem = navigationMenu.getItem(0);
459 MenuItem navigationBackMenuItem = navigationMenu.getItem(3);
460 MenuItem navigationForwardMenuItem = navigationMenu.getItem(4);
461 MenuItem navigationHistoryMenuItem = navigationMenu.getItem(5);
462 MenuItem navigationRequestsMenuItem = navigationMenu.getItem(6);
464 // Initialize the web view pager adapter.
465 webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
467 // Set the pager adapter on the web view pager.
468 webViewPager.setAdapter(webViewPagerAdapter);
470 // Store up to 100 tabs in memory.
471 webViewPager.setOffscreenPageLimit(100);
473 // Update the web view pager every time a tab is modified.
474 webViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
476 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
481 public void onPageSelected(int position) {
482 // Set the current WebView.
483 setCurrentWebView(position);
485 // Select the corresponding tab if it does not match the currently selected page. This will happen if the page was scrolled via swiping in the view pager or by creating a new tab.
486 if (tabLayout.getSelectedTabPosition() != position) {
487 // Create a handler to select the tab.
488 Handler selectTabHandler = new Handler();
490 // Create a runnable select the new tab.
491 Runnable selectTabRunnable = () -> {
492 // Get a handle for the tab.
493 TabLayout.Tab tab = tabLayout.getTabAt(position);
495 // Assert that the tab is not null.
502 // Select the tab layout after 100 milliseconds, which leaves enough time for a new tab to be created.
503 selectTabHandler.postDelayed(selectTabRunnable, 100);
508 public void onPageScrollStateChanged(int state) {
513 // Display the View SSL Certificate dialog when the currently selected tab is reselected.
514 tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
516 public void onTabSelected(TabLayout.Tab tab) {
517 // Select the same page in the view pager.
518 webViewPager.setCurrentItem(tab.getPosition());
522 public void onTabUnselected(TabLayout.Tab tab) {
527 public void onTabReselected(TabLayout.Tab tab) {
528 // Instantiate the View SSL Certificate dialog.
529 DialogFragment viewSslCertificateDialogFragment = ViewSslCertificateDialog.displayDialog(currentWebView.getWebViewFragmentId());
531 // Display the View SSL Certificate dialog.
532 viewSslCertificateDialogFragment.show(getSupportFragmentManager(), getString(R.string.view_ssl_certificate));
536 // Add the first tab.
539 // Set the bookmarks drawer resources according to the theme. This can't be done in the layout due to compatibility issues with the `DrawerLayout` support widget.
540 // The deprecated `getResources().getDrawable()` must be used until the minimum API >= 21 and and `getResources().getColor()` must be used until the minimum API >= 23.
542 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_dark));
543 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_dark));
544 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_dark));
545 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.gray_850));
547 launchBookmarksActivityFab.setImageDrawable(getResources().getDrawable(R.drawable.bookmarks_light));
548 createBookmarkFolderFab.setImageDrawable(getResources().getDrawable(R.drawable.create_folder_light));
549 createBookmarkFab.setImageDrawable(getResources().getDrawable(R.drawable.create_bookmark_light));
550 bookmarksListView.setBackgroundColor(getResources().getColor(R.color.white));
553 // Set the launch bookmarks activity FAB to launch the bookmarks activity.
554 launchBookmarksActivityFab.setOnClickListener(v -> {
555 // Get a copy of the favorite icon bitmap.
556 Bitmap favoriteIconBitmap = currentWebView.getFavoriteOrDefaultIcon();
558 // Create a favorite icon byte array output stream.
559 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
561 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
562 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
564 // Convert the favorite icon byte array stream to a byte array.
565 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
567 // Create an intent to launch the bookmarks activity.
568 Intent bookmarksIntent = new Intent(getApplicationContext(), BookmarksActivity.class);
570 // Add the extra information to the intent.
571 bookmarksIntent.putExtra("current_url", currentWebView.getUrl());
572 bookmarksIntent.putExtra("current_title", currentWebView.getTitle());
573 bookmarksIntent.putExtra("current_folder", currentBookmarksFolder);
574 bookmarksIntent.putExtra("favorite_icon_byte_array", favoriteIconByteArray);
577 startActivity(bookmarksIntent);
580 // Set the create new bookmark folder FAB to display an alert dialog.
581 createBookmarkFolderFab.setOnClickListener(v -> {
582 // Create a create bookmark folder dialog.
583 DialogFragment createBookmarkFolderDialog = CreateBookmarkFolderDialog.createBookmarkFolder(currentWebView.getFavoriteOrDefaultIcon());
585 // Show the create bookmark folder dialog.
586 createBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.create_folder));
589 // Set the create new bookmark FAB to display an alert dialog.
590 createBookmarkFab.setOnClickListener(view -> {
591 // Instantiate the create bookmark dialog.
592 DialogFragment createBookmarkDialog = CreateBookmarkDialog.createBookmark(currentWebView.getUrl(), currentWebView.getTitle(), currentWebView.getFavoriteOrDefaultIcon());
594 // Display the create bookmark dialog.
595 createBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.create_bookmark));
598 // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
599 findOnPageEditText.addTextChangedListener(new TextWatcher() {
601 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
606 public void onTextChanged(CharSequence s, int start, int before, int count) {
611 public void afterTextChanged(Editable s) {
612 // Search for the text in `mainWebView`.
613 currentWebView.findAllAsync(findOnPageEditText.getText().toString());
617 // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
618 findOnPageEditText.setOnKeyListener((v, keyCode, event) -> {
619 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed.
620 // Hide the soft keyboard.
621 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
623 // Consume the event.
625 } else { // A different key was pressed.
626 // Do not consume the event.
631 // Implement swipe to refresh.
632 swipeRefreshLayout.setOnRefreshListener(() -> currentWebView.reload());
634 // The swipe to refresh circle doesn't always hide itself completely unless it is moved up 10 pixels.
635 swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayout.getProgressViewStartOffset() - 10, swipeRefreshLayout.getProgressViewEndOffset());
637 // Set the swipe to refresh color according to the theme.
639 swipeRefreshLayout.setColorSchemeResources(R.color.blue_800);
640 swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.gray_850);
642 swipeRefreshLayout.setColorSchemeResources(R.color.blue_500);
645 // `DrawerTitle` identifies the `DrawerLayouts` in accessibility mode.
646 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
647 drawerLayout.setDrawerTitle(GravityCompat.END, getString(R.string.bookmarks));
649 // Initialize the bookmarks database helper. The `0` specifies a database version, but that is ignored and set instead using a constant in `BookmarksDatabaseHelper`.
650 bookmarksDatabaseHelper = new BookmarksDatabaseHelper(this, null, null, 0);
652 // Initialize `currentBookmarksFolder`. `""` is the home folder in the database.
653 currentBookmarksFolder = "";
655 // Load the home folder, which is `""` in the database.
656 loadBookmarksFolder();
658 bookmarksListView.setOnItemClickListener((parent, view, position, id) -> {
659 // Convert the id from long to int to match the format of the bookmarks database.
660 int databaseID = (int) id;
662 // Get the bookmark cursor for this ID and move it to the first row.
663 Cursor bookmarkCursor = bookmarksDatabaseHelper.getBookmark(databaseID);
664 bookmarkCursor.moveToFirst();
666 // Act upon the bookmark according to the type.
667 if (bookmarkCursor.getInt(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.IS_FOLDER)) == 1) { // The selected bookmark is a folder.
668 // Store the new folder name in `currentBookmarksFolder`.
669 currentBookmarksFolder = bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
671 // Load the new folder.
672 loadBookmarksFolder();
673 } else { // The selected bookmark is not a folder.
674 // Load the bookmark URL.
675 loadUrl(bookmarkCursor.getString(bookmarkCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_URL)));
677 // Close the bookmarks drawer.
678 drawerLayout.closeDrawer(GravityCompat.END);
681 // Close the `Cursor`.
682 bookmarkCursor.close();
685 bookmarksListView.setOnItemLongClickListener((parent, view, position, id) -> {
686 // Convert the database ID from `long` to `int`.
687 int databaseId = (int) id;
689 // Find out if the selected bookmark is a folder.
690 boolean isFolder = bookmarksDatabaseHelper.isFolder(databaseId);
693 // Save the current folder name, which is used in `onSaveEditBookmarkFolder()`.
694 oldFolderNameString = bookmarksCursor.getString(bookmarksCursor.getColumnIndex(BookmarksDatabaseHelper.BOOKMARK_NAME));
696 // Show the edit bookmark folder `AlertDialog` and name the instance `@string/edit_folder`.
697 DialogFragment editBookmarkFolderDialog = EditBookmarkFolderDialog.folderDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
698 editBookmarkFolderDialog.show(getSupportFragmentManager(), getString(R.string.edit_folder));
700 // Show the edit bookmark `AlertDialog` and name the instance `@string/edit_bookmark`.
701 DialogFragment editBookmarkDialog = EditBookmarkDialog.bookmarkDatabaseId(databaseId, currentWebView.getFavoriteOrDefaultIcon());
702 editBookmarkDialog.show(getSupportFragmentManager(), getString(R.string.edit_bookmark));
705 // Consume the event.
709 // Get the status bar pixel size.
710 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
711 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
713 // Get the resource density.
714 float screenDensity = getResources().getDisplayMetrics().density;
716 // Calculate the drawer header padding. This is used to move the text in the drawer headers below any cutouts.
717 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
718 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
719 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
721 // The drawer listener is used to update the navigation menu.`
722 drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
724 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
728 public void onDrawerOpened(@NonNull View drawerView) {
732 public void onDrawerClosed(@NonNull View drawerView) {
736 public void onDrawerStateChanged(int newState) {
737 if ((newState == DrawerLayout.STATE_SETTLING) || (newState == DrawerLayout.STATE_DRAGGING)) { // A drawer is opening or closing.
738 // Get handles for the drawer headers.
739 TextView navigationHeaderTextView = findViewById(R.id.navigationText);
740 TextView bookmarksHeaderTextView = findViewById(R.id.bookmarks_title_textview);
742 // Apply the navigation header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
743 if (navigationHeaderTextView != null) {
744 navigationHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
747 // Apply the bookmarks header paddings if the view is not null (sometimes it is null if another activity has already started). This moves the text in the header below any cutouts.
748 if (bookmarksHeaderTextView != null) {
749 bookmarksHeaderTextView.setPadding(drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingTop, drawerHeaderPaddingLeftAndRight, drawerHeaderPaddingBottom);
752 // Update the navigation menu items.
753 navigationCloseTabMenuItem.setEnabled(tabLayout.getTabCount() > 1);
754 navigationBackMenuItem.setEnabled(currentWebView.canGoBack());
755 navigationForwardMenuItem.setEnabled(currentWebView.canGoForward());
756 navigationHistoryMenuItem.setEnabled((currentWebView.canGoBack() || currentWebView.canGoForward()));
757 navigationRequestsMenuItem.setTitle(getString(R.string.requests) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
759 // Hide the keyboard (if displayed).
760 inputMethodManager.hideSoftInputFromWindow(currentWebView.getWindowToken(), 0);
762 // Clear the focus from from the URL text box and the WebView. This removes any text selection markers and context menus, which otherwise draw above the open drawers.
763 urlEditText.clearFocus();
764 currentWebView.clearFocus();
769 // Create the hamburger icon at the start of the AppBar.
770 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
772 // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
773 customHeaders.put("X-Requested-With", "");
775 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
776 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
778 // Inflate a bare WebView to get the default user agent. It is not used to render content on the screen.
779 @SuppressLint("InflateParams") View webViewLayout = getLayoutInflater().inflate(R.layout.bare_webview, null, false);
781 // Get a handle for the WebView.
782 WebView bareWebView = webViewLayout.findViewById(R.id.bare_webview);
784 // Store the default user agent.
785 webViewDefaultUserAgent = bareWebView.getSettings().getUserAgentString();
787 // Destroy the bare WebView.
788 bareWebView.destroy();
792 protected void onNewIntent(Intent intent) {
793 // Get the information from the intent.
794 String intentAction = intent.getAction();
795 Uri intentUriData = intent.getData();
797 // Only process the URI if it contains data. If the user pressed the desktop icon after the app was already running the URI will be null.
798 if (intentUriData != null) {
799 // Sets the new intent as the activity intent, which replaces the one that originally started the app.
805 // Create a URL string.
808 // If the intent action is a web search, perform the search.
809 if ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH)) {
810 // Create an encoded URL string.
811 String encodedUrlString;
813 // Sanitize the search input and convert it to a search.
815 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
816 } catch (UnsupportedEncodingException exception) {
817 encodedUrlString = "";
820 // Add the base search URL.
821 url = searchURL + encodedUrlString;
822 } else { // The intent should contain a URL.
823 // Set the intent data as the URL.
824 url = intentUriData.toString();
830 // Get a handle for the drawer layout.
831 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
833 // Close the navigation drawer if it is open.
834 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
835 drawerLayout.closeDrawer(GravityCompat.START);
838 // Close the bookmarks drawer if it is open.
839 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
840 drawerLayout.closeDrawer(GravityCompat.END);
843 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
844 currentWebView.requestFocus();
849 public void onRestart() {
850 // Run the default commands.
853 // Make sure Orbot is running if Privacy Browser is proxying through Orbot.
854 if (proxyThroughOrbot) {
855 // Request Orbot to start. If Orbot is already running no hard will be caused by this request.
856 Intent orbotIntent = new Intent("org.torproject.android.intent.action.START");
858 // Send the intent to the Orbot package.
859 orbotIntent.setPackage("org.torproject.android");
862 sendBroadcast(orbotIntent);
865 // Apply the app settings if returning from the Settings activity.
866 if (reapplyAppSettingsOnRestart) {
867 // Reset the reapply app settings on restart tracker.
868 reapplyAppSettingsOnRestart = false;
870 // Apply the app settings.
874 // Apply the domain settings if returning from the settings or domains activity.
875 if (reapplyDomainSettingsOnRestart) {
876 // Reset the reapply domain settings on restart tracker.
877 reapplyDomainSettingsOnRestart = false;
879 // Reapply the domain settings for each tab.
880 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
881 // Get the WebView tab fragment.
882 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
884 // Get the fragment view.
885 View fragmentView = webViewTabFragment.getView();
887 // Only reload the WebViews if they exist.
888 if (fragmentView != null) {
889 // Get the nested scroll WebView from the tab fragment.
890 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
892 // Reset the current domain name so the domain settings will be reapplied.
893 nestedScrollWebView.resetCurrentDomainName();
895 // Reapply the domain settings.
896 applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true);
901 // Load the URL on restart (used when loading a bookmark).
902 if (loadUrlOnRestart) {
903 // Load the specified URL.
904 loadUrl(urlToLoadOnRestart);
906 // Reset the load on restart tracker.
907 loadUrlOnRestart = false;
910 // Update the bookmarks drawer if returning from the Bookmarks activity.
911 if (restartFromBookmarksActivity) {
912 // Get a handle for the drawer layout.
913 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
915 // Close the bookmarks drawer.
916 drawerLayout.closeDrawer(GravityCompat.END);
918 // Reload the bookmarks drawer.
919 loadBookmarksFolder();
921 // Reset `restartFromBookmarksActivity`.
922 restartFromBookmarksActivity = false;
925 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
926 updatePrivacyIcons(true);
929 // `onResume()` runs after `onStart()`, which runs after `onCreate()` and `onRestart()`.
931 public void onResume() {
932 // Run the default commands.
935 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
936 // Get the WebView tab fragment.
937 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
939 // Get the fragment view.
940 View fragmentView = webViewTabFragment.getView();
942 // Only resume the WebViews if they exist (they won't when the app is first created).
943 if (fragmentView != null) {
944 // Get the nested scroll WebView from the tab fragment.
945 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
947 // Resume the nested scroll WebView JavaScript timers.
948 nestedScrollWebView.resumeTimers();
950 // Resume the nested scroll WebView.
951 nestedScrollWebView.onResume();
955 // Display a message to the user if waiting for Orbot.
956 if (waitingForOrbot && !orbotStatus.equals("ON")) {
957 // Disable the wide view port so that the waiting for Orbot text is displayed correctly.
958 currentWebView.getSettings().setUseWideViewPort(false);
960 // Load a waiting page. `null` specifies no encoding, which defaults to ASCII.
961 currentWebView.loadData("<html><body><br/><center><h1>" + getString(R.string.waiting_for_orbot) + "</h1></center></body></html>", "text/html", null);
964 if (displayingFullScreenVideo || inFullScreenBrowsingMode) {
965 // Get a handle for the root frame layouts.
966 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
968 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
969 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
971 /* Hide the system bars.
972 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
973 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
974 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
975 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
977 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
978 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
979 } else if (BuildConfig.FLAVOR.contentEquals("free")) { // Resume the adView for the free flavor.
981 AdHelper.resumeAd(findViewById(R.id.adview));
986 public void onPause() {
987 // Run the default commands.
990 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
991 // Get the WebView tab fragment.
992 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
994 // Get the fragment view.
995 View fragmentView = webViewTabFragment.getView();
997 // Only pause the WebViews if they exist (they won't when the app is first created).
998 if (fragmentView != null) {
999 // Get the nested scroll WebView from the tab fragment.
1000 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
1002 // Pause the nested scroll WebView.
1003 nestedScrollWebView.onPause();
1005 // Pause the nested scroll WebView JavaScript timers.
1006 nestedScrollWebView.pauseTimers();
1010 // Pause the ad or it will continue to consume resources in the background on the free flavor.
1011 if (BuildConfig.FLAVOR.contentEquals("free")) {
1013 AdHelper.pauseAd(findViewById(R.id.adview));
1018 public void onDestroy() {
1019 // Unregister the Orbot status broadcast receiver.
1020 this.unregisterReceiver(orbotStatusBroadcastReceiver);
1022 // Close the bookmarks cursor and database.
1023 bookmarksCursor.close();
1024 bookmarksDatabaseHelper.close();
1026 // Run the default commands.
1031 public boolean onCreateOptionsMenu(Menu menu) {
1032 // Inflate the menu. This adds items to the action bar if it is present.
1033 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
1035 // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
1038 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
1039 updatePrivacyIcons(false);
1041 // Get handles for the menu items.
1042 MenuItem toggleFirstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
1043 MenuItem toggleThirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
1044 MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
1045 MenuItem toggleSaveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
1046 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
1047 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
1048 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
1050 // Only display third-party cookies if API >= 21
1051 toggleThirdPartyCookiesMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
1053 // Only display the form data menu items if the API < 26.
1054 toggleSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
1055 clearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
1057 // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
1058 clearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
1060 // Only show Ad Consent if this is the free flavor.
1061 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
1063 // Get the shared preference values.
1064 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1066 // Get the dark theme and app bar preferences..
1067 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean("display_additional_app_bar_icons", false);
1068 boolean darkTheme = sharedPreferences.getBoolean("dark_theme", false);
1070 // 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.
1071 if (displayAdditionalAppBarIcons) {
1072 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1073 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1074 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
1075 } else { //Do not display the additional icons.
1076 toggleFirstPartyCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1077 toggleDomStorageMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1078 refreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1081 // Replace Refresh with Stop if a URL is already loading.
1082 if (currentWebView != null && currentWebView.getProgress() != 100) {
1084 refreshMenuItem.setTitle(R.string.stop);
1086 // If the icon is displayed in the AppBar, set it according to the theme.
1087 if (displayAdditionalAppBarIcons) {
1089 refreshMenuItem.setIcon(R.drawable.close_dark);
1091 refreshMenuItem.setIcon(R.drawable.close_light);
1100 public boolean onPrepareOptionsMenu(Menu menu) {
1101 // Get handles for the menu items.
1102 MenuItem addOrEditDomain = menu.findItem(R.id.add_or_edit_domain);
1103 MenuItem firstPartyCookiesMenuItem = menu.findItem(R.id.toggle_first_party_cookies);
1104 MenuItem thirdPartyCookiesMenuItem = menu.findItem(R.id.toggle_third_party_cookies);
1105 MenuItem domStorageMenuItem = menu.findItem(R.id.toggle_dom_storage);
1106 MenuItem saveFormDataMenuItem = menu.findItem(R.id.toggle_save_form_data); // Form data can be removed once the minimum API >= 26.
1107 MenuItem clearDataMenuItem = menu.findItem(R.id.clear_data);
1108 MenuItem clearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
1109 MenuItem clearDOMStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
1110 MenuItem clearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
1111 MenuItem blocklistsMenuItem = menu.findItem(R.id.blocklists);
1112 MenuItem easyListMenuItem = menu.findItem(R.id.easylist);
1113 MenuItem easyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
1114 MenuItem fanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
1115 MenuItem fanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
1116 MenuItem ultraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
1117 MenuItem blockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
1118 MenuItem fontSizeMenuItem = menu.findItem(R.id.font_size);
1119 MenuItem swipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
1120 MenuItem displayImagesMenuItem = menu.findItem(R.id.display_images);
1121 MenuItem nightModeMenuItem = menu.findItem(R.id.night_mode);
1122 MenuItem proxyThroughOrbotMenuItem = menu.findItem(R.id.proxy_through_orbot);
1124 // Get a handle for the cookie manager.
1125 CookieManager cookieManager = CookieManager.getInstance();
1127 // Initialize the current user agent string and the font size.
1128 String currentUserAgent = getString(R.string.user_agent_privacy_browser);
1131 // 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.
1132 if (currentWebView != null) {
1133 // Set the add or edit domain text.
1134 if (currentWebView.getDomainSettingsApplied()) {
1135 addOrEditDomain.setTitle(R.string.edit_domain_settings);
1137 addOrEditDomain.setTitle(R.string.add_domain_settings);
1140 // Get the current user agent from the WebView.
1141 currentUserAgent = currentWebView.getSettings().getUserAgentString();
1143 // Get the current font size from the
1144 fontSize = currentWebView.getSettings().getTextZoom();
1146 // Set the status of the menu item checkboxes.
1147 domStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1148 saveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
1149 easyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1150 easyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1151 fanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1152 fanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1153 ultraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1154 blockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1155 swipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
1156 displayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
1157 nightModeMenuItem.setChecked(currentWebView.getNightMode());
1159 // Initialize the display names for the blocklists with the number of blocked requests.
1160 blocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
1161 easyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_LIST) + " - " + getString(R.string.easylist));
1162 easyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASY_PRIVACY) + " - " + getString(R.string.easyprivacy));
1163 fanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
1164 fanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
1165 ultraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRA_PRIVACY) + " - " + getString(R.string.ultraprivacy));
1166 blockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
1168 // Only modify third-party cookies if the API >= 21.
1169 if (Build.VERSION.SDK_INT >= 21) {
1170 // Set the status of the third-party cookies checkbox.
1171 thirdPartyCookiesMenuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1173 // Enable third-party cookies if first-party cookies are enabled.
1174 thirdPartyCookiesMenuItem.setEnabled(cookieManager.acceptCookie());
1177 // Enable DOM Storage if JavaScript is enabled.
1178 domStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
1181 // Set the status of the menu item checkboxes.
1182 firstPartyCookiesMenuItem.setChecked(cookieManager.acceptCookie());
1183 proxyThroughOrbotMenuItem.setChecked(proxyThroughOrbot);
1185 // Enable Clear Cookies if there are any.
1186 clearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
1188 // 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`.
1189 String privateDataDirectoryString = getApplicationInfo().dataDir;
1191 // Get a count of the number of files in the Local Storage directory.
1192 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
1193 int localStorageDirectoryNumberOfFiles = 0;
1194 if (localStorageDirectory.exists()) {
1195 localStorageDirectoryNumberOfFiles = localStorageDirectory.list().length;
1198 // Get a count of the number of files in the IndexedDB directory.
1199 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
1200 int indexedDBDirectoryNumberOfFiles = 0;
1201 if (indexedDBDirectory.exists()) {
1202 indexedDBDirectoryNumberOfFiles = indexedDBDirectory.list().length;
1205 // Enable Clear DOM Storage if there is any.
1206 clearDOMStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
1208 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
1209 if (Build.VERSION.SDK_INT < 26) {
1210 // Get the WebView database.
1211 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1213 // Enable the clear form data menu item if there is anything to clear.
1214 clearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
1217 // Enable Clear Data if any of the submenu items are enabled.
1218 clearDataMenuItem.setEnabled(clearCookiesMenuItem.isEnabled() || clearDOMStorageMenuItem.isEnabled() || clearFormDataMenuItem.isEnabled());
1220 // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1221 fanboysSocialBlockingListMenuItem.setEnabled(!fanboysAnnoyanceListMenuItem.isChecked());
1223 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
1224 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
1225 menu.findItem(R.id.user_agent_privacy_browser).setChecked(true);
1226 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
1227 menu.findItem(R.id.user_agent_webview_default).setChecked(true);
1228 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
1229 menu.findItem(R.id.user_agent_firefox_on_android).setChecked(true);
1230 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
1231 menu.findItem(R.id.user_agent_chrome_on_android).setChecked(true);
1232 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
1233 menu.findItem(R.id.user_agent_safari_on_ios).setChecked(true);
1234 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
1235 menu.findItem(R.id.user_agent_firefox_on_linux).setChecked(true);
1236 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
1237 menu.findItem(R.id.user_agent_chromium_on_linux).setChecked(true);
1238 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
1239 menu.findItem(R.id.user_agent_firefox_on_windows).setChecked(true);
1240 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
1241 menu.findItem(R.id.user_agent_chrome_on_windows).setChecked(true);
1242 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
1243 menu.findItem(R.id.user_agent_edge_on_windows).setChecked(true);
1244 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
1245 menu.findItem(R.id.user_agent_internet_explorer_on_windows).setChecked(true);
1246 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
1247 menu.findItem(R.id.user_agent_safari_on_macos).setChecked(true);
1248 } else { // Custom user agent.
1249 menu.findItem(R.id.user_agent_custom).setChecked(true);
1252 // Instantiate the font size title and the selected font size menu item.
1253 String fontSizeTitle;
1254 MenuItem selectedFontSizeMenuItem;
1256 // Prepare the font size title and current size menu item.
1259 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.twenty_five_percent);
1260 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_twenty_five_percent);
1264 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.fifty_percent);
1265 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_fifty_percent);
1269 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.seventy_five_percent);
1270 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_seventy_five_percent);
1274 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
1275 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
1279 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_twenty_five_percent);
1280 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_twenty_five_percent);
1284 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_fifty_percent);
1285 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_fifty_percent);
1289 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_seventy_five_percent);
1290 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_seventy_five_percent);
1294 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.two_hundred_percent);
1295 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_two_hundred_percent);
1299 fontSizeTitle = getString(R.string.font_size) + " - " + getString(R.string.one_hundred_percent);
1300 selectedFontSizeMenuItem = menu.findItem(R.id.font_size_one_hundred_percent);
1304 // Set the font size title and select the current size menu item.
1305 fontSizeMenuItem.setTitle(fontSizeTitle);
1306 selectedFontSizeMenuItem.setChecked(true);
1308 // Run all the other default commands.
1309 super.onPrepareOptionsMenu(menu);
1311 // Display the menu.
1316 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
1317 @SuppressLint("SetJavaScriptEnabled")
1318 public boolean onOptionsItemSelected(MenuItem menuItem) {
1319 // Reenter full screen browsing mode if it was interrupted by the options menu. <https://redmine.stoutner.com/issues/389>
1320 if (inFullScreenBrowsingMode) {
1321 // Remove the translucent status flag. This is necessary so the root frame layout can fill the entire screen.
1322 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1324 FrameLayout rootFrameLayout = findViewById(R.id.root_framelayout);
1326 /* Hide the system bars.
1327 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1328 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
1329 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1330 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
1332 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1333 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1336 // Get the selected menu item ID.
1337 int menuItemId = menuItem.getItemId();
1339 // Get a handle for the shared preferences.
1340 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1342 // Get a handle for the cookie manager.
1343 CookieManager cookieManager = CookieManager.getInstance();
1345 // Run the commands that correlate to the selected menu item.
1346 switch (menuItemId) {
1347 case R.id.toggle_javascript:
1348 // Toggle the JavaScript status.
1349 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1351 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1352 updatePrivacyIcons(true);
1354 // Display a `Snackbar`.
1355 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled.
1356 Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1357 } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled.
1358 Snackbar.make(findViewById(R.id.webviewpager), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1359 } else { // Privacy mode.
1360 Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1363 // Reload the current WebView.
1364 currentWebView.reload();
1367 case R.id.add_or_edit_domain:
1368 if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings.
1369 // Reapply the domain settings on returning to `MainWebViewActivity`.
1370 reapplyDomainSettingsOnRestart = true;
1372 // Create an intent to launch the domains activity.
1373 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1375 // Add the extra information to the intent.
1376 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1377 domainsIntent.putExtra("close_on_back", true);
1378 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1380 // Get the current certificate.
1381 SslCertificate sslCertificate = currentWebView.getCertificate();
1383 // Check to see if the SSL certificate is populated.
1384 if (sslCertificate != null) {
1385 // Extract the certificate to strings.
1386 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1387 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1388 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1389 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1390 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1391 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1392 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1393 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1395 // Add the certificate to the intent.
1396 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1397 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1398 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1399 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1400 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1401 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1402 domainsIntent.putExtra("ssl_start_date", startDateLong);
1403 domainsIntent.putExtra("ssl_end_date", endDateLong);
1406 // Check to see if the current IP addresses have been received.
1407 if (currentWebView.hasCurrentIpAddresses()) {
1408 // Add the current IP addresses to the intent.
1409 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1413 startActivity(domainsIntent);
1414 } else { // Add a new domain.
1415 // Apply the new domain settings on returning to `MainWebViewActivity`.
1416 reapplyDomainSettingsOnRestart = true;
1418 // Get the current domain
1419 Uri currentUri = Uri.parse(currentWebView.getUrl());
1420 String currentDomain = currentUri.getHost();
1422 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1423 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1425 // Create the domain and store the database ID.
1426 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1428 // Create an intent to launch the domains activity.
1429 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1431 // Add the extra information to the intent.
1432 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1433 domainsIntent.putExtra("close_on_back", true);
1434 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1436 // Get the current certificate.
1437 SslCertificate sslCertificate = currentWebView.getCertificate();
1439 // Check to see if the SSL certificate is populated.
1440 if (sslCertificate != null) {
1441 // Extract the certificate to strings.
1442 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1443 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1444 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1445 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1446 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1447 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1448 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1449 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1451 // Add the certificate to the intent.
1452 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1453 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1454 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1455 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1456 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1457 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1458 domainsIntent.putExtra("ssl_start_date", startDateLong);
1459 domainsIntent.putExtra("ssl_end_date", endDateLong);
1462 // Check to see if the current IP addresses have been received.
1463 if (currentWebView.hasCurrentIpAddresses()) {
1464 // Add the current IP addresses to the intent.
1465 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1469 startActivity(domainsIntent);
1473 case R.id.toggle_first_party_cookies:
1474 // Switch the first-party cookie status.
1475 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1477 // Store the first-party cookie status.
1478 currentWebView.setAcceptFirstPartyCookies(cookieManager.acceptCookie());
1480 // Update the menu checkbox.
1481 menuItem.setChecked(cookieManager.acceptCookie());
1483 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1484 updatePrivacyIcons(true);
1486 // Display a snackbar.
1487 if (cookieManager.acceptCookie()) { // First-party cookies are enabled.
1488 Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1489 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
1490 Snackbar.make(findViewById(R.id.webviewpager), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1491 } else { // Privacy mode.
1492 Snackbar.make(findViewById(R.id.webviewpager), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1495 // Reload the current WebView.
1496 currentWebView.reload();
1499 case R.id.toggle_third_party_cookies:
1500 if (Build.VERSION.SDK_INT >= 21) {
1501 // Switch the status of thirdPartyCookiesEnabled.
1502 cookieManager.setAcceptThirdPartyCookies(currentWebView, !cookieManager.acceptThirdPartyCookies(currentWebView));
1504 // Update the menu checkbox.
1505 menuItem.setChecked(cookieManager.acceptThirdPartyCookies(currentWebView));
1507 // Display a snackbar.
1508 if (cookieManager.acceptThirdPartyCookies(currentWebView)) {
1509 Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
1511 Snackbar.make(findViewById(R.id.webviewpager), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
1514 // Reload the current WebView.
1515 currentWebView.reload();
1516 } // Else do nothing because SDK < 21.
1519 case R.id.toggle_dom_storage:
1520 // Toggle the status of domStorageEnabled.
1521 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1523 // Update the menu checkbox.
1524 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1526 // Update the privacy icon. `true` refreshes the app bar icons.
1527 updatePrivacyIcons(true);
1529 // Display a snackbar.
1530 if (currentWebView.getSettings().getDomStorageEnabled()) {
1531 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1533 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1536 // Reload the current WebView.
1537 currentWebView.reload();
1540 // Form data can be removed once the minimum API >= 26.
1541 case R.id.toggle_save_form_data:
1542 // Switch the status of saveFormDataEnabled.
1543 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1545 // Update the menu checkbox.
1546 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1548 // Display a snackbar.
1549 if (currentWebView.getSettings().getSaveFormData()) {
1550 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1552 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1555 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1556 updatePrivacyIcons(true);
1558 // Reload the current WebView.
1559 currentWebView.reload();
1562 case R.id.clear_cookies:
1563 Snackbar.make(findViewById(R.id.webviewpager), R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1564 .setAction(R.string.undo, v -> {
1565 // Do nothing because everything will be handled by `onDismissed()` below.
1567 .addCallback(new Snackbar.Callback() {
1568 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1570 public void onDismissed(Snackbar snackbar, int event) {
1571 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1572 // Delete the cookies, which command varies by SDK.
1573 if (Build.VERSION.SDK_INT < 21) {
1574 cookieManager.removeAllCookie();
1576 cookieManager.removeAllCookies(null);
1584 case R.id.clear_dom_storage:
1585 Snackbar.make(findViewById(R.id.webviewpager), R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1586 .setAction(R.string.undo, v -> {
1587 // Do nothing because everything will be handled by `onDismissed()` below.
1589 .addCallback(new Snackbar.Callback() {
1590 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1592 public void onDismissed(Snackbar snackbar, int event) {
1593 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1594 // Delete the DOM Storage.
1595 WebStorage webStorage = WebStorage.getInstance();
1596 webStorage.deleteAllData();
1598 // Initialize a handler to manually delete the DOM storage files and directories.
1599 Handler deleteDomStorageHandler = new Handler();
1601 // Setup a runnable to manually delete the DOM storage files and directories.
1602 Runnable deleteDomStorageRunnable = () -> {
1604 // Get a handle for the runtime.
1605 Runtime runtime = Runtime.getRuntime();
1607 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1608 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1609 String privateDataDirectoryString = getApplicationInfo().dataDir;
1611 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1612 Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1614 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1615 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1616 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1617 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1618 Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1620 // Wait for the processes to finish.
1621 deleteLocalStorageProcess.waitFor();
1622 deleteIndexProcess.waitFor();
1623 deleteQuotaManagerProcess.waitFor();
1624 deleteQuotaManagerJournalProcess.waitFor();
1625 deleteDatabasesProcess.waitFor();
1626 } catch (Exception exception) {
1627 // Do nothing if an error is thrown.
1631 // Manually delete the DOM storage files after 200 milliseconds.
1632 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1639 // Form data can be remove once the minimum API >= 26.
1640 case R.id.clear_form_data:
1641 Snackbar.make(findViewById(R.id.webviewpager), R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1642 .setAction(R.string.undo, v -> {
1643 // Do nothing because everything will be handled by `onDismissed()` below.
1645 .addCallback(new Snackbar.Callback() {
1646 @SuppressLint("SwitchIntDef") // Ignore the lint warning about not handling the other possible events as they are covered by `default:`.
1648 public void onDismissed(Snackbar snackbar, int event) {
1649 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1650 // Delete the form data.
1651 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1652 mainWebViewDatabase.clearFormData();
1660 // Toggle the EasyList status.
1661 currentWebView.enableBlocklist(NestedScrollWebView.EASY_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1663 // Update the menu checkbox.
1664 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_LIST));
1666 // Reload the current WebView.
1667 currentWebView.reload();
1670 case R.id.easyprivacy:
1671 // Toggle the EasyPrivacy status.
1672 currentWebView.enableBlocklist(NestedScrollWebView.EASY_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1674 // Update the menu checkbox.
1675 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASY_PRIVACY));
1677 // Reload the current WebView.
1678 currentWebView.reload();
1681 case R.id.fanboys_annoyance_list:
1682 // Toggle Fanboy's Annoyance List status.
1683 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1685 // Update the menu checkbox.
1686 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1688 // Update the staus of Fanboy's Social Blocking List.
1689 MenuItem fanboysSocialBlockingListMenuItem = optionsMenu.findItem(R.id.fanboys_social_blocking_list);
1690 fanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1692 // Reload the current WebView.
1693 currentWebView.reload();
1696 case R.id.fanboys_social_blocking_list:
1697 // Toggle Fanboy's Social Blocking List status.
1698 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1700 // Update the menu checkbox.
1701 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1703 // Reload the current WebView.
1704 currentWebView.reload();
1707 case R.id.ultraprivacy:
1708 // Toggle the UltraPrivacy status.
1709 currentWebView.enableBlocklist(NestedScrollWebView.ULTRA_PRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1711 // Update the menu checkbox.
1712 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRA_PRIVACY));
1714 // Reload the current WebView.
1715 currentWebView.reload();
1718 case R.id.block_all_third_party_requests:
1719 //Toggle the third-party requests blocker status.
1720 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1722 // Update the menu checkbox.
1723 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1725 // Reload the current WebView.
1726 currentWebView.reload();
1729 case R.id.user_agent_privacy_browser:
1730 // Update the user agent.
1731 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1733 // Reload the current WebView.
1734 currentWebView.reload();
1737 case R.id.user_agent_webview_default:
1738 // Update the user agent.
1739 currentWebView.getSettings().setUserAgentString("");
1741 // Reload the current WebView.
1742 currentWebView.reload();
1745 case R.id.user_agent_firefox_on_android:
1746 // Update the user agent.
1747 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1749 // Reload the current WebView.
1750 currentWebView.reload();
1753 case R.id.user_agent_chrome_on_android:
1754 // Update the user agent.
1755 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1757 // Reload the current WebView.
1758 currentWebView.reload();
1761 case R.id.user_agent_safari_on_ios:
1762 // Update the user agent.
1763 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1765 // Reload the current WebView.
1766 currentWebView.reload();
1769 case R.id.user_agent_firefox_on_linux:
1770 // Update the user agent.
1771 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1773 // Reload the current WebView.
1774 currentWebView.reload();
1777 case R.id.user_agent_chromium_on_linux:
1778 // Update the user agent.
1779 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1781 // Reload the current WebView.
1782 currentWebView.reload();
1785 case R.id.user_agent_firefox_on_windows:
1786 // Update the user agent.
1787 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1789 // Reload the current WebView.
1790 currentWebView.reload();
1793 case R.id.user_agent_chrome_on_windows:
1794 // Update the user agent.
1795 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1797 // Reload the current WebView.
1798 currentWebView.reload();
1801 case R.id.user_agent_edge_on_windows:
1802 // Update the user agent.
1803 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1805 // Reload the current WebView.
1806 currentWebView.reload();
1809 case R.id.user_agent_internet_explorer_on_windows:
1810 // Update the user agent.
1811 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1813 // Reload the current WebView.
1814 currentWebView.reload();
1817 case R.id.user_agent_safari_on_macos:
1818 // Update the user agent.
1819 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1821 // Reload the current WebView.
1822 currentWebView.reload();
1825 case R.id.user_agent_custom:
1826 // Update the user agent.
1827 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1829 // Reload the current WebView.
1830 currentWebView.reload();
1833 case R.id.font_size_twenty_five_percent:
1834 currentWebView.getSettings().setTextZoom(25);
1837 case R.id.font_size_fifty_percent:
1838 currentWebView.getSettings().setTextZoom(50);
1841 case R.id.font_size_seventy_five_percent:
1842 currentWebView.getSettings().setTextZoom(75);
1845 case R.id.font_size_one_hundred_percent:
1846 currentWebView.getSettings().setTextZoom(100);
1849 case R.id.font_size_one_hundred_twenty_five_percent:
1850 currentWebView.getSettings().setTextZoom(125);
1853 case R.id.font_size_one_hundred_fifty_percent:
1854 currentWebView.getSettings().setTextZoom(150);
1857 case R.id.font_size_one_hundred_seventy_five_percent:
1858 currentWebView.getSettings().setTextZoom(175);
1861 case R.id.font_size_two_hundred_percent:
1862 currentWebView.getSettings().setTextZoom(200);
1865 case R.id.swipe_to_refresh:
1866 // Toggle the stored status of swipe to refresh.
1867 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1869 // Get a handle for the swipe refresh layout.
1870 SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
1872 // Update the swipe refresh layout.
1873 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
1874 if (Build.VERSION.SDK_INT >= 23) { // For API >= 23, the status of the scroll refresh listener is continuously updated by the on scroll change listener.
1875 // Only enable the swipe refresh layout if the WebView is scrolled to the top.
1876 swipeRefreshLayout.setEnabled(currentWebView.getY() == 0);
1877 } else { // For API < 23, the swipe refresh layout is always enabled.
1878 // Enable the swipe refresh layout.
1879 swipeRefreshLayout.setEnabled(true);
1881 } else { // Swipe to refresh is disabled.
1882 // Disable the swipe refresh layout.
1883 swipeRefreshLayout.setEnabled(false);
1887 case R.id.display_images:
1888 if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
1889 // Disable loading of images.
1890 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1892 // Reload the website to remove existing images.
1893 currentWebView.reload();
1894 } else { // Images are not currently loaded automatically.
1895 // Enable loading of images. Missing images will be loaded without the need for a reload.
1896 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1900 case R.id.night_mode:
1901 // Toggle night mode.
1902 currentWebView.setNightMode(!currentWebView.getNightMode());
1904 // Enable or disable JavaScript according to night mode, the global preference, and any domain settings.
1905 if (currentWebView.getNightMode()) { // Night mode is enabled, which requires JavaScript.
1906 // Enable JavaScript.
1907 currentWebView.getSettings().setJavaScriptEnabled(true);
1908 } else if (currentWebView.getDomainSettingsApplied()) { // Night mode is disabled and domain settings are applied. Set JavaScript according to the domain settings.
1909 // Apply the JavaScript preference that was stored the last time domain settings were loaded.
1910 currentWebView.getSettings().setJavaScriptEnabled(currentWebView.getDomainSettingsJavaScriptEnabled());
1911 } else { // Night mode is disabled and domain settings are not applied. Set JavaScript according to the global preference.
1912 // Apply the JavaScript preference.
1913 currentWebView.getSettings().setJavaScriptEnabled(sharedPreferences.getBoolean("javascript", false));
1916 // Update the privacy icons.
1917 updatePrivacyIcons(false);
1919 // Reload the website.
1920 currentWebView.reload();
1923 case R.id.find_on_page:
1924 // Get a handle for the views.
1925 Toolbar toolbar = findViewById(R.id.toolbar);
1926 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1927 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1929 // Hide the toolbar.
1930 toolbar.setVisibility(View.GONE);
1932 // Show the find on page linear layout.
1933 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1935 // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
1936 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1937 findOnPageEditText.postDelayed(() -> {
1938 // Set the focus on `findOnPageEditText`.
1939 findOnPageEditText.requestFocus();
1941 // Get a handle for the input method manager.
1942 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1944 // Remove the lint warning below that the input method manager might be null.
1945 assert inputMethodManager != null;
1947 // Display the keyboard. `0` sets no input flags.
1948 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1952 case R.id.view_source:
1953 // Create an intent to launch the view source activity.
1954 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1956 // Add the variables to the intent.
1957 viewSourceIntent.putExtra("user_agent", currentWebView.getSettings().getUserAgentString());
1958 viewSourceIntent.putExtra("current_url", currentWebView.getUrl());
1961 startActivity(viewSourceIntent);
1964 case R.id.share_url:
1965 // Setup the share string.
1966 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1968 // Create the share intent.
1969 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1970 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1971 shareIntent.setType("text/plain");
1974 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1978 // Get a print manager instance.
1979 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1981 // Remove the lint error below that print manager might be null.
1982 assert printManager != null;
1984 // Create a print document adapter from the current WebView.
1985 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1987 // Print the document.
1988 printManager.print(getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1991 case R.id.open_with_app:
1992 openWithApp(currentWebView.getUrl());
1995 case R.id.open_with_browser:
1996 openWithBrowser(currentWebView.getUrl());
1999 case R.id.add_to_homescreen:
2000 // Instantiate the create home screen shortcut dialog.
2001 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
2002 currentWebView.getFavoriteOrDefaultIcon());
2004 // Show the create home screen shortcut dialog.
2005 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
2008 case R.id.proxy_through_orbot:
2009 // Toggle the proxy through Orbot variable.
2010 proxyThroughOrbot = !proxyThroughOrbot;
2012 // Apply the proxy through Orbot settings.
2013 applyProxyThroughOrbot(true);
2017 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
2018 // Reload the current WebView.
2019 currentWebView.reload();
2020 } else { // The stop button was pushed.
2021 // Stop the loading of the WebView.
2022 currentWebView.stopLoading();
2026 case R.id.ad_consent:
2027 // Display the ad consent dialog.
2028 DialogFragment adConsentDialogFragment = new AdConsentDialog();
2029 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
2033 // Don't consume the event.
2034 return super.onOptionsItemSelected(menuItem);
2038 // removeAllCookies is deprecated, but it is required for API < 21.
2040 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
2041 // Get the menu item ID.
2042 int menuItemId = menuItem.getItemId();
2044 // Get a handle for the shared preferences.
2045 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2047 // Run the commands that correspond to the selected menu item.
2048 switch (menuItemId) {
2049 case R.id.close_tab:
2050 // Get a handle for the tab layout and the view pager.
2051 TabLayout tabLayout = findViewById(R.id.tablayout);
2052 ViewPager webViewPager = findViewById(R.id.webviewpager);
2054 // Get the current tab number.
2055 int currentTabNumber = tabLayout.getSelectedTabPosition();
2057 // Delete the current tab.
2058 tabLayout.removeTabAt(currentTabNumber);
2060 // Delete the current page. If the selected page number did not change during the delete, it will return true, meaning that the current WebView must be reset.
2061 if (webViewPagerAdapter.deletePage(currentTabNumber, webViewPager)) {
2062 setCurrentWebView(currentTabNumber);
2066 case R.id.clear_and_exit:
2067 // Close the bookmarks cursor and database.
2068 bookmarksCursor.close();
2069 bookmarksDatabaseHelper.close();
2071 // Get the status of the clear everything preference.
2072 boolean clearEverything = sharedPreferences.getBoolean("clear_everything", true);
2074 // Get a handle for the runtime.
2075 Runtime runtime = Runtime.getRuntime();
2077 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
2078 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
2079 String privateDataDirectoryString = getApplicationInfo().dataDir;
2082 if (clearEverything || sharedPreferences.getBoolean("clear_cookies", true)) {
2083 // The command to remove cookies changed slightly in API 21.
2084 if (Build.VERSION.SDK_INT >= 21) {
2085 CookieManager.getInstance().removeAllCookies(null);
2087 CookieManager.getInstance().removeAllCookie();
2090 // Manually delete the cookies database, as `CookieManager` sometimes will not flush its changes to disk before `System.exit(0)` is run.
2092 // Two commands must be used because `Runtime.exec()` does not like `*`.
2093 Process deleteCookiesProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies");
2094 Process deleteCookiesJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/Cookies-journal");
2096 // Wait until the processes have finished.
2097 deleteCookiesProcess.waitFor();
2098 deleteCookiesJournalProcess.waitFor();
2099 } catch (Exception exception) {
2100 // Do nothing if an error is thrown.
2104 // Clear DOM storage.
2105 if (clearEverything || sharedPreferences.getBoolean("clear_dom_storage", true)) {
2106 // Ask `WebStorage` to clear the DOM storage.
2107 WebStorage webStorage = WebStorage.getInstance();
2108 webStorage.deleteAllData();
2110 // Manually delete the DOM storage files and directories, as `WebStorage` sometimes will not flush its changes to disk before `System.exit(0)` is run.
2112 // A `String[]` must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2113 Process deleteLocalStorageProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
2115 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
2116 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
2117 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
2118 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
2119 Process deleteDatabaseProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
2121 // Wait until the processes have finished.
2122 deleteLocalStorageProcess.waitFor();
2123 deleteIndexProcess.waitFor();
2124 deleteQuotaManagerProcess.waitFor();
2125 deleteQuotaManagerJournalProcess.waitFor();
2126 deleteDatabaseProcess.waitFor();
2127 } catch (Exception exception) {
2128 // Do nothing if an error is thrown.
2132 // Clear form data if the API < 26.
2133 if ((Build.VERSION.SDK_INT < 26) && (clearEverything || sharedPreferences.getBoolean("clear_form_data", true))) {
2134 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
2135 webViewDatabase.clearFormData();
2137 // Manually delete the form data database, as `WebViewDatabase` sometimes will not flush its changes to disk before `System.exit(0)` is run.
2139 // A string array must be used because the database contains a space and `Runtime.exec` will not otherwise escape the string correctly.
2140 Process deleteWebDataProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data"});
2141 Process deleteWebDataJournalProcess = runtime.exec(new String[] {"rm", "-f", privateDataDirectoryString + "/app_webview/Web Data-journal"});
2143 // Wait until the processes have finished.
2144 deleteWebDataProcess.waitFor();
2145 deleteWebDataJournalProcess.waitFor();
2146 } catch (Exception exception) {
2147 // Do nothing if an error is thrown.
2152 if (clearEverything || sharedPreferences.getBoolean("clear_cache", true)) {
2153 // Clear the cache from each WebView.
2154 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
2155 // Get the WebView tab fragment.
2156 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
2158 // Get the fragment view.
2159 View fragmentView = webViewTabFragment.getView();
2161 // Only clear the cache if the WebView exists.
2162 if (fragmentView != null) {
2163 // Get the nested scroll WebView from the tab fragment.
2164 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
2166 // Clear the cache for this WebView.
2167 nestedScrollWebView.clearCache(true);
2171 // Manually delete the cache directories.
2173 // Delete the main cache directory.
2174 Process deleteCacheProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/cache");
2176 // Delete the secondary `Service Worker` cache directory.
2177 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
2178 Process deleteServiceWorkerProcess = runtime.exec(new String[] {"rm", "-rf", privateDataDirectoryString + "/app_webview/Service Worker/"});
2180 // Wait until the processes have finished.
2181 deleteCacheProcess.waitFor();
2182 deleteServiceWorkerProcess.waitFor();
2183 } catch (Exception exception) {
2184 // Do nothing if an error is thrown.
2188 // Wipe out each WebView.
2189 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
2190 // Get the WebView tab fragment.
2191 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
2193 // Get the fragment view.
2194 View fragmentView = webViewTabFragment.getView();
2196 // Only wipe out the WebView if it exists.
2197 if (fragmentView != null) {
2198 // Get the nested scroll WebView from the tab fragment.
2199 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
2201 // Clear SSL certificate preferences for this WebView.
2202 nestedScrollWebView.clearSslPreferences();
2204 // Clear the back/forward history for this WebView.
2205 nestedScrollWebView.clearHistory();
2207 // Destroy the internal state of `mainWebView`.
2208 nestedScrollWebView.destroy();
2212 // Clear the custom headers.
2213 customHeaders.clear();
2215 // Manually delete the `app_webview` folder, which contains the cookies, DOM storage, form data, and `Service Worker` cache.
2216 // See `https://code.google.com/p/android/issues/detail?id=233826&thanks=233826&ts=1486670530`.
2217 if (clearEverything) {
2219 // Delete the folder.
2220 Process deleteAppWebviewProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview");
2222 // Wait until the process has finished.
2223 deleteAppWebviewProcess.waitFor();
2224 } catch (Exception exception) {
2225 // Do nothing if an error is thrown.
2229 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
2230 if (Build.VERSION.SDK_INT >= 21) {
2231 finishAndRemoveTask();
2236 // Remove the terminated program from RAM. The status code is `0`.
2241 // Select the homepage based on the proxy through Orbot status.
2242 if (proxyThroughOrbot) {
2243 // Load the Tor homepage.
2244 loadUrl(sharedPreferences.getString("tor_homepage", getString(R.string.tor_homepage_default_value)));
2246 // Load the normal homepage.
2247 loadUrl(sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
2252 if (currentWebView.canGoBack()) {
2253 // Reset the current domain name so that navigation works if third-party requests are blocked.
2254 currentWebView.resetCurrentDomainName();
2256 // Set navigating history so that the domain settings are applied when the new URL is loaded.
2257 currentWebView.setNavigatingHistory(true);
2259 // Load the previous website in the history.
2260 currentWebView.goBack();
2265 if (currentWebView.canGoForward()) {
2266 // Reset the current domain name so that navigation works if third-party requests are blocked.
2267 currentWebView.resetCurrentDomainName();
2269 // Set navigating history so that the domain settings are applied when the new URL is loaded.
2270 currentWebView.setNavigatingHistory(true);
2272 // Load the next website in the history.
2273 currentWebView.goForward();
2278 // Instantiate the URL history dialog.
2279 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
2281 // Show the URL history dialog.
2282 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2286 // Populate the resource requests.
2287 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
2289 // Create an intent to launch the Requests activity.
2290 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2292 // Add the block third-party requests status to the intent.
2293 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
2296 startActivity(requestsIntent);
2299 case R.id.downloads:
2300 // Launch the system Download Manager.
2301 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2303 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
2304 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2306 startActivity(downloadManagerIntent);
2310 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2311 reapplyDomainSettingsOnRestart = true;
2313 // Launch the domains activity.
2314 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2316 // Add the extra information to the intent.
2317 domainsIntent.putExtra("current_url", currentWebView.getUrl());
2319 // Get the current certificate.
2320 SslCertificate sslCertificate = currentWebView.getCertificate();
2322 // Check to see if the SSL certificate is populated.
2323 if (sslCertificate != null) {
2324 // Extract the certificate to strings.
2325 String issuedToCName = sslCertificate.getIssuedTo().getCName();
2326 String issuedToOName = sslCertificate.getIssuedTo().getOName();
2327 String issuedToUName = sslCertificate.getIssuedTo().getUName();
2328 String issuedByCName = sslCertificate.getIssuedBy().getCName();
2329 String issuedByOName = sslCertificate.getIssuedBy().getOName();
2330 String issuedByUName = sslCertificate.getIssuedBy().getUName();
2331 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2332 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2334 // Add the certificate to the intent.
2335 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2336 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2337 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2338 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2339 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2340 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2341 domainsIntent.putExtra("ssl_start_date", startDateLong);
2342 domainsIntent.putExtra("ssl_end_date", endDateLong);
2345 // Check to see if the current IP addresses have been received.
2346 if (currentWebView.hasCurrentIpAddresses()) {
2347 // Add the current IP addresses to the intent.
2348 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2352 startActivity(domainsIntent);
2356 // Set the flag to reapply app settings on restart when returning from Settings.
2357 reapplyAppSettingsOnRestart = true;
2359 // Set the flag to reapply the domain settings on restart when returning from Settings.
2360 reapplyDomainSettingsOnRestart = true;
2362 // Launch the settings activity.
2363 Intent settingsIntent = new Intent(this, SettingsActivity.class);
2364 startActivity(settingsIntent);
2367 case R.id.import_export:
2368 // Launch the import/export activity.
2369 Intent importExportIntent = new Intent (this, ImportExportActivity.class);
2370 startActivity(importExportIntent);
2374 // Launch the logcat activity.
2375 Intent logcatIntent = new Intent(this, LogcatActivity.class);
2376 startActivity(logcatIntent);
2380 // Launch `GuideActivity`.
2381 Intent guideIntent = new Intent(this, GuideActivity.class);
2382 startActivity(guideIntent);
2386 // Create an intent to launch the about activity.
2387 Intent aboutIntent = new Intent(this, AboutActivity.class);
2389 // Create a string array for the blocklist versions.
2390 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],
2391 ultraPrivacy.get(0).get(0)[0]};
2393 // Add the blocklist versions to the intent.
2394 aboutIntent.putExtra("blocklist_versions", blocklistVersions);
2397 startActivity(aboutIntent);
2401 // Get a handle for the drawer layout.
2402 DrawerLayout drawerLayout = findViewById(R.id.drawerlayout);
2404 // Close the navigation drawer.
2405 drawerLayout.closeDrawer(GravityCompat.START);
2410 public void onPostCreate(Bundle savedInstanceState) {
2411 // Run the default commands.
2412 super.onPostCreate(savedInstanceState);
2414 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
2415 actionBarDrawerToggle.syncState();
2419 public void onConfigurationChanged(Configuration newConfig) {
2420 // Run the default commands.
2421 super.onConfigurationChanged(newConfig);
2423 // Get the status bar pixel size.
2424 int statusBarResourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
2425 int statusBarPixelSize = getResources().getDimensionPixelSize(statusBarResourceId);
2427 // Get the resource density.
2428 float screenDensity = getResources().getDisplayMetrics().density;
2430 // Recalculate the drawer header padding.
2431 drawerHeaderPaddingLeftAndRight = (int) (15 * screenDensity);
2432 drawerHeaderPaddingTop = statusBarPixelSize + (int) (4 * screenDensity);
2433 drawerHeaderPaddingBottom = (int) (8 * screenDensity);
2435 // Reload the ad for the free flavor if not in full screen mode.
2436 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2437 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2438 AdHelper.loadAd(findViewById(R.id.adview), getApplicationContext(), getString(R.string.ad_unit_id));
2441 // `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:
2442 // https://code.google.com/p/android/issues/detail?id=20493#c8
2443 // ActivityCompat.invalidateOptionsMenu(this);
2447 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2448 // Store the hit test result.
2449 final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2451 // Create the URL strings.
2452 final String imageUrl;
2453 final String linkUrl;
2455 // Get handles for the system managers.
2456 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2457 FragmentManager fragmentManager = getSupportFragmentManager();
2458 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
2460 // Remove the lint errors below that the clipboard manager might be null.
2461 assert clipboardManager != null;
2463 // Process the link according to the type.
2464 switch (hitTestResult.getType()) {
2465 // `SRC_ANCHOR_TYPE` is a link.
2466 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2467 // Get the target URL.
2468 linkUrl = hitTestResult.getExtra();
2470 // Set the target URL as the title of the `ContextMenu`.
2471 menu.setHeaderTitle(linkUrl);
2473 // Add a Load URL entry.
2474 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2483 // Add an Open with App entry.
2484 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2485 openWithApp(linkUrl);
2489 // Add an Open with Browser entry.
2490 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2491 openWithBrowser(linkUrl);
2495 // Add a Copy URL entry.
2496 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2497 // Save the link URL in a `ClipData`.
2498 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2500 // Set the `ClipData` as the clipboard's primary clip.
2501 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2505 // Add a Download URL entry.
2506 menu.add(R.string.download_url).setOnMenuItemClickListener((MenuItem item) -> {
2507 // Check if the download should be processed by an external app.
2508 if (sharedPreferences.getBoolean("download_with_external_app", false)) { // Download with an external app.
2509 openUrlWithExternalApp(linkUrl);
2510 } else { // Download with Android's download manager.
2511 // Check to see if the storage permission has already been granted.
2512 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { // The storage permission needs to be requested.
2513 // Store the variables for future use by `onRequestPermissionsResult()`.
2514 downloadUrl = linkUrl;
2515 downloadContentDisposition = "none";
2516 downloadContentLength = -1;
2518 // Show a dialog if the user has previously denied the permission.
2519 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show a dialog explaining the request first.
2520 // Instantiate the download location permission alert dialog and set the download type to DOWNLOAD_FILE.
2521 DialogFragment downloadLocationPermissionDialogFragment = DownloadLocationPermissionDialog.downloadType(DownloadLocationPermissionDialog.DOWNLOAD_FILE);
2523 // Show the download location permission alert dialog. The permission will be requested when the the dialog is closed.
2524 downloadLocationPermissionDialogFragment.show(fragmentManager, getString(R.string.download_location));
2525 } else { // Show the permission request directly.
2526 // Request the permission. The download dialog will be launched by `onRequestPermissionResult()`.
2527 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, DOWNLOAD_FILE_REQUEST_CODE);
2529 } else { // The storage permission has already been granted.
2530 // Get a handle for the download file alert dialog.
2531 DialogFragment downloadFileDialogFragment = DownloadFileDialog.fromUrl(linkUrl, "none", -1);
2533 // Show the download file alert dialog.
2534 downloadFileDialogFragment.show(fragmentManager, getString(R.string.download));
2540 // Add a Cancel entry, which by default closes the context menu.
2541 menu.add(R.string.cancel);
2544 case WebView.HitTestResult.EMAIL_TYPE:
2545 // Get the target URL.
2546 linkUrl = hitTestResult.getExtra();
2548 // Set the target URL as the title of the `ContextMenu`.
2549 menu.setHeaderTitle(linkUrl);
2551 // Add a Write Email entry.
2552 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2553 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2554 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2556 // Parse the url and set it as the data for the `Intent`.
2557 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2559 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2560 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2563 startActivity(emailIntent);