2 * Copyright © 2015-2021 Soren Stoutner <soren@stoutner.com>.
4 * Download cookie code contributed 2017 Hendrik Knackstedt. Copyright assigned to Soren Stoutner <soren@stoutner.com>.
6 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
8 * Privacy Browser is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Privacy Browser is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
22 package com.stoutner.privacybrowser.activities;
24 import android.annotation.SuppressLint;
25 import android.app.Activity;
26 import android.app.Dialog;
27 import android.app.DownloadManager;
28 import android.app.SearchManager;
29 import android.content.ActivityNotFoundException;
30 import android.content.BroadcastReceiver;
31 import android.content.ClipData;
32 import android.content.ClipboardManager;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.content.SharedPreferences;
37 import android.content.pm.PackageManager;
38 import android.content.res.Configuration;
39 import android.database.Cursor;
40 import android.graphics.Bitmap;
41 import android.graphics.BitmapFactory;
42 import android.graphics.Typeface;
43 import android.graphics.drawable.BitmapDrawable;
44 import android.graphics.drawable.Drawable;
45 import android.net.Uri;
46 import android.net.http.SslCertificate;
47 import android.net.http.SslError;
48 import android.os.AsyncTask;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.Environment;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.preference.PreferenceManager;
55 import android.print.PrintDocumentAdapter;
56 import android.print.PrintManager;
57 import android.provider.DocumentsContract;
58 import android.text.Editable;
59 import android.text.Spanned;
60 import android.text.TextWatcher;
61 import android.text.style.ForegroundColorSpan;
62 import android.util.Patterns;
63 import android.util.TypedValue;
64 import android.view.ContextMenu;
65 import android.view.GestureDetector;
66 import android.view.KeyEvent;
67 import android.view.Menu;
68 import android.view.MenuItem;
69 import android.view.MotionEvent;
70 import android.view.View;
71 import android.view.ViewGroup;
72 import android.view.WindowManager;
73 import android.view.inputmethod.InputMethodManager;
74 import android.webkit.CookieManager;
75 import android.webkit.HttpAuthHandler;
76 import android.webkit.SslErrorHandler;
77 import android.webkit.ValueCallback;
78 import android.webkit.WebBackForwardList;
79 import android.webkit.WebChromeClient;
80 import android.webkit.WebResourceResponse;
81 import android.webkit.WebSettings;
82 import android.webkit.WebStorage;
83 import android.webkit.WebView;
84 import android.webkit.WebViewClient;
85 import android.webkit.WebViewDatabase;
86 import android.widget.ArrayAdapter;
87 import android.widget.CheckBox;
88 import android.widget.CursorAdapter;
89 import android.widget.EditText;
90 import android.widget.FrameLayout;
91 import android.widget.ImageView;
92 import android.widget.LinearLayout;
93 import android.widget.ListView;
94 import android.widget.ProgressBar;
95 import android.widget.RadioButton;
96 import android.widget.RelativeLayout;
97 import android.widget.TextView;
99 import androidx.annotation.NonNull;
100 import androidx.appcompat.app.ActionBar;
101 import androidx.appcompat.app.ActionBarDrawerToggle;
102 import androidx.appcompat.app.AppCompatActivity;
103 import androidx.appcompat.app.AppCompatDelegate;
104 import androidx.appcompat.widget.Toolbar;
105 import androidx.coordinatorlayout.widget.CoordinatorLayout;
106 import androidx.core.content.res.ResourcesCompat;
107 import androidx.core.view.GravityCompat;
108 import androidx.drawerlayout.widget.DrawerLayout;
109 import androidx.fragment.app.DialogFragment;
110 import androidx.fragment.app.Fragment;
111 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
112 import androidx.viewpager.widget.ViewPager;
113 import androidx.webkit.WebSettingsCompat;
114 import androidx.webkit.WebViewFeature;
116 import com.google.android.material.appbar.AppBarLayout;
117 import com.google.android.material.floatingactionbutton.FloatingActionButton;
118 import com.google.android.material.navigation.NavigationView;
119 import com.google.android.material.snackbar.Snackbar;
120 import com.google.android.material.tabs.TabLayout;
122 import com.stoutner.privacybrowser.BuildConfig;
123 import com.stoutner.privacybrowser.R;
124 import com.stoutner.privacybrowser.adapters.WebViewPagerAdapter;
125 import com.stoutner.privacybrowser.asynctasks.GetHostIpAddresses;
126 import com.stoutner.privacybrowser.asynctasks.PopulateBlocklists;
127 import com.stoutner.privacybrowser.asynctasks.PrepareSaveDialog;
128 import com.stoutner.privacybrowser.asynctasks.SaveUrl;
129 import com.stoutner.privacybrowser.asynctasks.SaveWebpageImage;
130 import com.stoutner.privacybrowser.definitions.PendingDialog;
131 import com.stoutner.privacybrowser.dialogs.AdConsentDialog;
132 import com.stoutner.privacybrowser.dialogs.CreateBookmarkDialog;
133 import com.stoutner.privacybrowser.dialogs.CreateBookmarkFolderDialog;
134 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcutDialog;
135 import com.stoutner.privacybrowser.dialogs.EditBookmarkFolderDialog;
136 import com.stoutner.privacybrowser.dialogs.FontSizeDialog;
137 import com.stoutner.privacybrowser.dialogs.HttpAuthenticationDialog;
138 import com.stoutner.privacybrowser.dialogs.OpenDialog;
139 import com.stoutner.privacybrowser.dialogs.ProxyNotInstalledDialog;
140 import com.stoutner.privacybrowser.dialogs.PinnedMismatchDialog;
141 import com.stoutner.privacybrowser.dialogs.SaveWebpageDialog;
142 import com.stoutner.privacybrowser.dialogs.SslCertificateErrorDialog;
143 import com.stoutner.privacybrowser.dialogs.UrlHistoryDialog;
144 import com.stoutner.privacybrowser.dialogs.ViewSslCertificateDialog;
145 import com.stoutner.privacybrowser.dialogs.WaitingForProxyDialog;
146 import com.stoutner.privacybrowser.fragments.WebViewTabFragment;
147 import com.stoutner.privacybrowser.helpers.AdHelper;
148 import com.stoutner.privacybrowser.helpers.BlocklistHelper;
149 import com.stoutner.privacybrowser.helpers.BookmarksDatabaseHelper;
150 import com.stoutner.privacybrowser.helpers.DomainsDatabaseHelper;
151 import com.stoutner.privacybrowser.helpers.ProxyHelper;
152 import com.stoutner.privacybrowser.views.NestedScrollWebView;
154 import java.io.ByteArrayInputStream;
155 import java.io.ByteArrayOutputStream;
157 import java.io.FileInputStream;
158 import java.io.FileOutputStream;
159 import java.io.IOException;
160 import java.io.InputStream;
161 import java.io.OutputStream;
162 import java.io.UnsupportedEncodingException;
164 import java.net.MalformedURLException;
166 import java.net.URLDecoder;
167 import java.net.URLEncoder;
169 import java.text.NumberFormat;
171 import java.util.ArrayList;
172 import java.util.Calendar;
173 import java.util.Date;
174 import java.util.HashMap;
175 import java.util.HashSet;
176 import java.util.List;
177 import java.util.Map;
178 import java.util.Objects;
179 import java.util.Set;
180 import java.util.concurrent.ExecutorService;
181 import java.util.concurrent.Executors;
183 public class MainWebViewActivity extends AppCompatActivity implements CreateBookmarkDialog.CreateBookmarkListener, CreateBookmarkFolderDialog.CreateBookmarkFolderListener,
184 EditBookmarkFolderDialog.EditBookmarkFolderListener, FontSizeDialog.UpdateFontSizeListener, NavigationView.OnNavigationItemSelectedListener, OpenDialog.OpenListener,
185 PinnedMismatchDialog.PinnedMismatchListener, PopulateBlocklists.PopulateBlocklistsListener, SaveWebpageDialog.SaveWebpageListener, UrlHistoryDialog.NavigateHistoryListener,
186 WebViewTabFragment.NewTabListener {
188 // The executor service handles background tasks. It is accessed from `ViewSourceActivity`.
189 public static ExecutorService executorService = Executors.newFixedThreadPool(4);
191 // `orbotStatus` is public static so it can be accessed from `OrbotProxyHelper`. It is also used in `onCreate()`, `onResume()`, and `applyProxy()`.
192 public static String orbotStatus = "unknown";
194 // The WebView pager adapter is accessed from `HttpAuthenticationDialog`, `PinnedMismatchDialog`, and `SslCertificateErrorDialog`. It is also used in `onCreate()`, `onResume()`, and `addTab()`.
195 public static WebViewPagerAdapter webViewPagerAdapter;
197 // `restartFromBookmarksActivity` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onRestart()`.
198 public static boolean restartFromBookmarksActivity;
200 // Define the public static variables.
201 public static ArrayList<PendingDialog> pendingDialogsArrayList = new ArrayList<>();
203 // `currentBookmarksFolder` is public static so it can be accessed from `BookmarksActivity`. It is also used in `onCreate()`, `onBackPressed()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`,
204 // `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
205 public static String currentBookmarksFolder;
207 // The user agent constants are public static so they can be accessed from `SettingsFragment`, `DomainsActivity`, and `DomainSettingsFragment`.
208 public final static int UNRECOGNIZED_USER_AGENT = -1;
209 public final static int SETTINGS_WEBVIEW_DEFAULT_USER_AGENT = 1;
210 public final static int SETTINGS_CUSTOM_USER_AGENT = 12;
211 public final static int DOMAINS_SYSTEM_DEFAULT_USER_AGENT = 0;
212 public final static int DOMAINS_WEBVIEW_DEFAULT_USER_AGENT = 2;
213 public final static int DOMAINS_CUSTOM_USER_AGENT = 13;
215 // Define the start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`.
216 private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 0;
217 public final static int BROWSE_OPEN_REQUEST_CODE = 1;
218 public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 2;
220 // The proxy mode is public static so it can be accessed from `ProxyHelper()`.
221 // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`.
222 // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes.
223 public static String proxyMode = ProxyHelper.NONE;
225 // Define the saved instance state constants.
226 private final String SAVED_STATE_ARRAY_LIST = "saved_state_array_list";
227 private final String SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST = "saved_nested_scroll_webview_state_array_list";
228 private final String SAVED_TAB_POSITION = "saved_tab_position";
229 private final String PROXY_MODE = "proxy_mode";
231 // Define the saved instance state variables.
232 private ArrayList<Bundle> savedStateArrayList;
233 private ArrayList<Bundle> savedNestedScrollWebViewStateArrayList;
234 private int savedTabPosition;
235 private String savedProxyMode;
237 // Define the class variables.
238 @SuppressWarnings("rawtypes")
239 AsyncTask populateBlocklists;
241 // The current WebView is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`,
242 // `findNextOnPage()`, `closeFindOnPage()`, `loadUrlFromTextBox()`, `onSslMismatchBack()`, `applyProxy()`, and `applyDomainSettings()`.
243 private NestedScrollWebView currentWebView;
245 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
246 private final Map<String, String> customHeaders = new HashMap<>();
248 // The search URL is set in `applyAppSettings()` and used in `onNewIntent()`, `loadUrlFromTextBox()`, `initializeApp()`, and `initializeWebView()`.
249 private String searchURL;
251 // The blocklists are populated in `finishedPopulatingBlocklists()` and accessed from `initializeWebView()`.
252 private ArrayList<List<String[]>> easyList;
253 private ArrayList<List<String[]>> easyPrivacy;
254 private ArrayList<List<String[]>> fanboysAnnoyanceList;
255 private ArrayList<List<String[]>> fanboysSocialList;
256 private ArrayList<List<String[]>> ultraList;
257 private ArrayList<List<String[]>> ultraPrivacy;
259 // The action bar drawer toggle is initialized in `onCreate()` and used in `onResume()`.
260 private ActionBarDrawerToggle actionBarDrawerToggle;
262 // The color spans are used in `onCreate()` and `highlightUrlText()`.
263 private ForegroundColorSpan redColorSpan;
264 private ForegroundColorSpan initialGrayColorSpan;
265 private ForegroundColorSpan finalGrayColorSpan;
267 // `bookmarksDatabaseHelper` is used in `onCreate()`, `onDestroy`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`,
268 // and `loadBookmarksFolder()`.
269 private BookmarksDatabaseHelper bookmarksDatabaseHelper;
271 // `bookmarksCursor` is used in `onDestroy()`, `onOptionsItemSelected()`, `onCreateBookmark()`, `onCreateBookmarkFolder()`, `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
272 private Cursor bookmarksCursor;
274 // `bookmarksCursorAdapter` is used in `onCreateBookmark()`, `onCreateBookmarkFolder()` `onSaveEditBookmark()`, `onSaveEditBookmarkFolder()`, and `loadBookmarksFolder()`.
275 private CursorAdapter bookmarksCursorAdapter;
277 // `oldFolderNameString` is used in `onCreate()` and `onSaveEditBookmarkFolder()`.
278 private String oldFolderNameString;
280 // `fileChooserCallback` is used in `onCreate()` and `onActivityResult()`.
281 private ValueCallback<Uri[]> fileChooserCallback;
283 // The default progress view offsets are set in `onCreate()` and used in `initializeWebView()`.
284 private int appBarHeight;
285 private int defaultProgressViewStartOffset;
286 private int defaultProgressViewEndOffset;
288 // The URL sanitizers are set in `applyAppSettings()` and used in `sanitizeUrl()`.
289 private boolean sanitizeGoogleAnalytics;
290 private boolean sanitizeFacebookClickIds;
291 private boolean sanitizeTwitterAmpRedirects;
293 // Declare the class variables
294 private BroadcastReceiver orbotStatusBroadcastReceiver;
295 private String webViewDefaultUserAgent;
296 private boolean incognitoModeEnabled;
297 private boolean fullScreenBrowsingModeEnabled;
298 private boolean inFullScreenBrowsingMode;
299 private boolean downloadWithExternalApp;
300 private boolean hideAppBar;
301 private boolean scrollAppBar;
302 private boolean bottomAppBar;
303 private boolean loadingNewIntent;
304 private boolean reapplyDomainSettingsOnRestart;
305 private boolean reapplyAppSettingsOnRestart;
306 private boolean displayingFullScreenVideo;
307 private boolean waitingForProxy;
309 // Define the class variables.
310 private long lastScrollUpdate = 0;
312 // Declare the class views.
313 private FrameLayout rootFrameLayout;
314 private DrawerLayout drawerLayout;
315 private RelativeLayout mainContentRelativeLayout;
316 private AppBarLayout appBarLayout;
317 private Toolbar toolbar;
318 private RelativeLayout urlRelativeLayout;
319 private EditText urlEditText;
320 private ActionBar actionBar;
321 private LinearLayout findOnPageLinearLayout;
322 private LinearLayout tabsLinearLayout;
323 private TabLayout tabLayout;
324 private SwipeRefreshLayout swipeRefreshLayout;
325 private ViewPager webViewPager;
326 private FrameLayout fullScreenVideoFrameLayout;
328 // Declare the class menus.
329 private Menu optionsMenu;
331 // Declare the class menu items.
332 private MenuItem navigationBackMenuItem;
333 private MenuItem navigationForwardMenuItem;
334 private MenuItem navigationHistoryMenuItem;
335 private MenuItem navigationRequestsMenuItem;
336 private MenuItem optionsPrivacyMenuItem;
337 private MenuItem optionsRefreshMenuItem;
338 private MenuItem optionsCookiesMenuItem;
339 private MenuItem optionsDomStorageMenuItem;
340 private MenuItem optionsSaveFormDataMenuItem;
341 private MenuItem optionsClearDataMenuItem;
342 private MenuItem optionsClearCookiesMenuItem;
343 private MenuItem optionsClearDomStorageMenuItem;
344 private MenuItem optionsClearFormDataMenuItem;
345 private MenuItem optionsBlocklistsMenuItem;
346 private MenuItem optionsEasyListMenuItem;
347 private MenuItem optionsEasyPrivacyMenuItem;
348 private MenuItem optionsFanboysAnnoyanceListMenuItem;
349 private MenuItem optionsFanboysSocialBlockingListMenuItem;
350 private MenuItem optionsUltraListMenuItem;
351 private MenuItem optionsUltraPrivacyMenuItem;
352 private MenuItem optionsBlockAllThirdPartyRequestsMenuItem;
353 private MenuItem optionsProxyMenuItem;
354 private MenuItem optionsProxyNoneMenuItem;
355 private MenuItem optionsProxyTorMenuItem;
356 private MenuItem optionsProxyI2pMenuItem;
357 private MenuItem optionsProxyCustomMenuItem;
358 private MenuItem optionsUserAgentMenuItem;
359 private MenuItem optionsUserAgentPrivacyBrowserMenuItem;
360 private MenuItem optionsUserAgentWebViewDefaultMenuItem;
361 private MenuItem optionsUserAgentFirefoxOnAndroidMenuItem;
362 private MenuItem optionsUserAgentChromeOnAndroidMenuItem;
363 private MenuItem optionsUserAgentSafariOnIosMenuItem;
364 private MenuItem optionsUserAgentFirefoxOnLinuxMenuItem;
365 private MenuItem optionsUserAgentChromiumOnLinuxMenuItem;
366 private MenuItem optionsUserAgentFirefoxOnWindowsMenuItem;
367 private MenuItem optionsUserAgentChromeOnWindowsMenuItem;
368 private MenuItem optionsUserAgentEdgeOnWindowsMenuItem;
369 private MenuItem optionsUserAgentInternetExplorerOnWindowsMenuItem;
370 private MenuItem optionsUserAgentSafariOnMacosMenuItem;
371 private MenuItem optionsUserAgentCustomMenuItem;
372 private MenuItem optionsSwipeToRefreshMenuItem;
373 private MenuItem optionsWideViewportMenuItem;
374 private MenuItem optionsDisplayImagesMenuItem;
375 private MenuItem optionsDarkWebViewMenuItem;
376 private MenuItem optionsFontSizeMenuItem;
377 private MenuItem optionsAddOrEditDomainMenuItem;
380 // Remove the warning about needing to override `performClick()` when using an `OnTouchListener` with `WebView`.
381 @SuppressLint("ClickableViewAccessibility")
382 protected void onCreate(Bundle savedInstanceState) {
383 // Run the default commands.
384 super.onCreate(savedInstanceState);
386 // Check to see if the activity has been restarted.
387 if (savedInstanceState != null) {
388 // Store the saved instance state variables.
389 savedStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_STATE_ARRAY_LIST);
390 savedNestedScrollWebViewStateArrayList = savedInstanceState.getParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST);
391 savedTabPosition = savedInstanceState.getInt(SAVED_TAB_POSITION);
392 savedProxyMode = savedInstanceState.getString(PROXY_MODE);
395 // Initialize the default preference values the first time the program is run. `false` keeps this command from resetting any current preferences back to default.
396 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
398 // Get a handle for the shared preferences.
399 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
401 // Get the preferences.
402 String appTheme = sharedPreferences.getString("app_theme", getString(R.string.app_theme_default_value));
403 boolean allowScreenshots = sharedPreferences.getBoolean(getString(R.string.allow_screenshots_key), false);
404 bottomAppBar = sharedPreferences.getBoolean(getString(R.string.bottom_app_bar_key), false);
406 // Get the theme entry values string array.
407 String[] appThemeEntryValuesStringArray = getResources().getStringArray(R.array.app_theme_entry_values);
409 // Set the app theme according to the preference. A switch statement cannot be used because the theme entry values string array is not a compile time constant.
410 if (appTheme.equals(appThemeEntryValuesStringArray[1])) { // The light theme is selected.
411 // Apply the light theme.
412 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
413 } else if (appTheme.equals(appThemeEntryValuesStringArray[2])) { // The dark theme is selected.
414 // Apply the dark theme.
415 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
416 } else { // The system default theme is selected.
417 if (Build.VERSION.SDK_INT >= 28) { // The system default theme is supported.
418 // Follow the system default theme.
419 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
420 } else { // The system default theme is not supported.
421 // Follow the battery saver mode.
422 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
426 // Disable screenshots if not allowed.
427 if (!allowScreenshots) {
428 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
431 // Enable the drawing of the entire webpage. This makes it possible to save a website image. This must be done before anything else happens with the WebView.
432 if (Build.VERSION.SDK_INT >= 21) {
433 WebView.enableSlowWholeDocumentDraw();
437 setTheme(R.style.PrivacyBrowser);
439 // Set the content view.
441 setContentView(R.layout.main_framelayout_bottom_appbar);
443 setContentView(R.layout.main_framelayout_top_appbar);
446 // Get handles for the views.
447 rootFrameLayout = findViewById(R.id.root_framelayout);
448 drawerLayout = findViewById(R.id.drawerlayout);
449 mainContentRelativeLayout = findViewById(R.id.main_content_relativelayout);
450 appBarLayout = findViewById(R.id.appbar_layout);
451 toolbar = findViewById(R.id.toolbar);
452 findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
453 tabsLinearLayout = findViewById(R.id.tabs_linearlayout);
454 tabLayout = findViewById(R.id.tablayout);
455 swipeRefreshLayout = findViewById(R.id.swiperefreshlayout);
456 webViewPager = findViewById(R.id.webviewpager);
457 fullScreenVideoFrameLayout = findViewById(R.id.full_screen_video_framelayout);
459 // Get a handle for the navigation view.
460 NavigationView navigationView = findViewById(R.id.navigationview);
462 // Get a handle for the navigation menu.
463 Menu navigationMenu = navigationView.getMenu();
465 // Get handles for the navigation menu items.
466 navigationBackMenuItem = navigationMenu.findItem(R.id.back);
467 navigationForwardMenuItem = navigationMenu.findItem(R.id.forward);
468 navigationHistoryMenuItem = navigationMenu.findItem(R.id.history);
469 navigationRequestsMenuItem = navigationMenu.findItem(R.id.requests);
471 // Listen for touches on the navigation menu.
472 navigationView.setNavigationItemSelectedListener(this);
474 // Get a handle for the app compat delegate.
475 AppCompatDelegate appCompatDelegate = getDelegate();
477 // Set the support action bar.
478 appCompatDelegate.setSupportActionBar(toolbar);
480 // Get a handle for the action bar.
481 actionBar = appCompatDelegate.getSupportActionBar();
483 // Remove the incorrect lint warning below that the action bar might be null.
484 assert actionBar != null;
486 // Add the custom layout, which shows the URL text bar.
487 actionBar.setCustomView(R.layout.url_app_bar);
488 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
490 // Get handles for the views in the URL app bar.
491 urlRelativeLayout = findViewById(R.id.url_relativelayout);
492 urlEditText = findViewById(R.id.url_edittext);
494 // Create the hamburger icon at the start of the AppBar.
495 actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
497 // Initially disable the sliding drawers. They will be enabled once the blocklists are loaded.
498 drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
500 // Initialize the web view pager adapter.
501 webViewPagerAdapter = new WebViewPagerAdapter(getSupportFragmentManager());
503 // Set the pager adapter on the web view pager.
504 webViewPager.setAdapter(webViewPagerAdapter);
506 // Store up to 100 tabs in memory.
507 webViewPager.setOffscreenPageLimit(100);
509 // Initialize the app.
512 // Apply the app settings from the shared preferences.
515 // Populate the blocklists.
516 populateBlocklists = new PopulateBlocklists(this, this).execute();
520 protected void onNewIntent(Intent intent) {
521 // Run the default commands.
522 super.onNewIntent(intent);
524 // Replace the intent that started the app with this one.
527 // Check to see if the app is being restarted from a saved state.
528 if (savedStateArrayList == null || savedStateArrayList.size() == 0) { // The activity is not being restarted from a saved state.
529 // Get the information from the intent.
530 String intentAction = intent.getAction();
531 Uri intentUriData = intent.getData();
532 String intentStringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
534 // Determine if this is a web search.
535 boolean isWebSearch = ((intentAction != null) && intentAction.equals(Intent.ACTION_WEB_SEARCH));
537 // Only process the URI if it contains data or it is a web search. If the user pressed the desktop icon after the app was already running the URI will be null.
538 if (intentUriData != null || intentStringExtra != null || isWebSearch) {
539 // Exit the full screen video if it is displayed.
540 if (displayingFullScreenVideo) {
541 // Exit full screen video mode.
542 exitFullScreenVideo();
544 // Reload the current WebView. Otherwise, it can display entirely black.
545 currentWebView.reload();
548 // Get the shared preferences.
549 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
551 // Create a URL string.
554 // If the intent action is a web search, perform the search.
555 if (isWebSearch) { // The intent is a web search.
556 // Create an encoded URL string.
557 String encodedUrlString;
559 // Sanitize the search input and convert it to a search.
561 encodedUrlString = URLEncoder.encode(intent.getStringExtra(SearchManager.QUERY), "UTF-8");
562 } catch (UnsupportedEncodingException exception) {
563 encodedUrlString = "";
566 // Add the base search URL.
567 url = searchURL + encodedUrlString;
568 } else if (intentUriData != null) { // The intent contains a URL formatted as a URI.
569 // Set the intent data as the URL.
570 url = intentUriData.toString();
571 } else { // The intent contains a string, which might be a URL.
572 // Set the intent string as the URL.
573 url = intentStringExtra;
576 // Add a new tab if specified in the preferences.
577 if (sharedPreferences.getBoolean("open_intents_in_new_tab", true)) { // Load the URL in a new tab.
578 // Set the loading new intent flag.
579 loadingNewIntent = true;
582 addNewTab(url, true);
583 } else { // Load the URL in the current tab.
585 loadUrl(currentWebView, url);
588 // Close the navigation drawer if it is open.
589 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
590 drawerLayout.closeDrawer(GravityCompat.START);
593 // Close the bookmarks drawer if it is open.
594 if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
595 drawerLayout.closeDrawer(GravityCompat.END);
602 public void onRestart() {
603 // Run the default commands.
606 // Apply the app settings if returning from the Settings activity.
607 if (reapplyAppSettingsOnRestart) {
608 // Reset the reapply app settings on restart tracker.
609 reapplyAppSettingsOnRestart = false;
611 // Apply the app settings.
615 // Apply the domain settings if returning from the settings or domains activity.
616 if (reapplyDomainSettingsOnRestart) {
617 // Reset the reapply domain settings on restart tracker.
618 reapplyDomainSettingsOnRestart = false;
620 // Reapply the domain settings for each tab.
621 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
622 // Get the WebView tab fragment.
623 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
625 // Get the fragment view.
626 View fragmentView = webViewTabFragment.getView();
628 // Only reload the WebViews if they exist.
629 if (fragmentView != null) {
630 // Get the nested scroll WebView from the tab fragment.
631 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
633 // Reset the current domain name so the domain settings will be reapplied.
634 nestedScrollWebView.resetCurrentDomainName();
636 // Reapply the domain settings if the URL is not null, which can happen if an empty tab is active when returning from settings.
637 if (nestedScrollWebView.getUrl() != null) {
638 applyDomainSettings(nestedScrollWebView, nestedScrollWebView.getUrl(), false, true, false);
644 // Update the bookmarks drawer if returning from the Bookmarks activity.
645 if (restartFromBookmarksActivity) {
646 // Close the bookmarks drawer.
647 drawerLayout.closeDrawer(GravityCompat.END);
649 // Reload the bookmarks drawer.
650 loadBookmarksFolder();
652 // Reset `restartFromBookmarksActivity`.
653 restartFromBookmarksActivity = false;
656 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step. This can be important if the screen was rotated.
657 updatePrivacyIcons(true);
660 // `onStart()` runs after `onCreate()` or `onRestart()`. This is used instead of `onResume()` so the commands aren't called every time the screen is partially hidden.
662 public void onStart() {
663 // Run the default commands.
666 // Resume any WebViews.
667 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
668 // Get the WebView tab fragment.
669 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
671 // Get the fragment view.
672 View fragmentView = webViewTabFragment.getView();
674 // Only resume the WebViews if they exist (they won't when the app is first created).
675 if (fragmentView != null) {
676 // Get the nested scroll WebView from the tab fragment.
677 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
679 // Resume the nested scroll WebView.
680 nestedScrollWebView.onResume();
684 // Resume the nested scroll WebView JavaScript timers. This is a global command that resumes JavaScript timers on all WebViews.
685 if (currentWebView != null) {
686 currentWebView.resumeTimers();
689 // Reapply the proxy settings if the system is using a proxy. This redisplays the appropriate alert dialog.
690 if (!proxyMode.equals(ProxyHelper.NONE)) {
694 // Reapply any system UI flags and the ad in the free flavor.
695 if (displayingFullScreenVideo || inFullScreenBrowsingMode) { // The system is displaying a website or a video in full screen mode.
696 /* Hide the system bars.
697 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
698 * SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN makes the root frame layout fill the area that is normally reserved for the status bar.
699 * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
700 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically re-hides them after they are shown.
702 rootFrameLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
703 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
704 } else if (BuildConfig.FLAVOR.contentEquals("free")) { // The system in not in full screen mode.
705 // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
706 View adView = findViewById(R.id.adview);
709 AdHelper.resumeAd(adView);
712 // Show any pending dialogs.
713 for (int i = 0; i < pendingDialogsArrayList.size(); i++) {
714 // Get the pending dialog from the array list.
715 PendingDialog pendingDialog = pendingDialogsArrayList.get(i);
717 // Show the pending dialog.
718 pendingDialog.dialogFragment.show(getSupportFragmentManager(), pendingDialog.tag);
721 // Clear the pending dialogs array list.
722 pendingDialogsArrayList.clear();
725 // `onStop()` runs after `onPause()`. It is used instead of `onPause()` so the commands are not called every time the screen is partially hidden.
727 public void onStop() {
728 // Run the default commands.
731 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
732 // Get the WebView tab fragment.
733 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
735 // Get the fragment view.
736 View fragmentView = webViewTabFragment.getView();
738 // Only pause the WebViews if they exist (they won't when the app is first created).
739 if (fragmentView != null) {
740 // Get the nested scroll WebView from the tab fragment.
741 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
743 // Pause the nested scroll WebView.
744 nestedScrollWebView.onPause();
748 // Pause the WebView JavaScript timers. This is a global command that pauses JavaScript on all WebViews.
749 if (currentWebView != null) {
750 currentWebView.pauseTimers();
753 // Pause the ad or it will continue to consume resources in the background on the free flavor.
754 if (BuildConfig.FLAVOR.contentEquals("free")) {
755 // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
756 View adView = findViewById(R.id.adview);
759 AdHelper.pauseAd(adView);
764 public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
765 // Run the default commands.
766 super.onSaveInstanceState(savedInstanceState);
768 // Create the saved state array lists.
769 ArrayList<Bundle> savedStateArrayList = new ArrayList<>();
770 ArrayList<Bundle> savedNestedScrollWebViewStateArrayList = new ArrayList<>();
772 // Get the URLs from each tab.
773 for (int i = 0; i < webViewPagerAdapter.getCount(); i++) {
774 // Get the WebView tab fragment.
775 WebViewTabFragment webViewTabFragment = webViewPagerAdapter.getPageFragment(i);
777 // Get the fragment view.
778 View fragmentView = webViewTabFragment.getView();
780 if (fragmentView != null) {
781 // Get the nested scroll WebView from the tab fragment.
782 NestedScrollWebView nestedScrollWebView = fragmentView.findViewById(R.id.nestedscroll_webview);
784 // Create saved state bundle.
785 Bundle savedStateBundle = new Bundle();
787 // Get the current states.
788 nestedScrollWebView.saveState(savedStateBundle);
789 Bundle savedNestedScrollWebViewStateBundle = nestedScrollWebView.saveNestedScrollWebViewState();
791 // Store the saved states in the array lists.
792 savedStateArrayList.add(savedStateBundle);
793 savedNestedScrollWebViewStateArrayList.add(savedNestedScrollWebViewStateBundle);
797 // Get the current tab position.
798 int currentTabPosition = tabLayout.getSelectedTabPosition();
800 // Store the saved states in the bundle.
801 savedInstanceState.putParcelableArrayList(SAVED_STATE_ARRAY_LIST, savedStateArrayList);
802 savedInstanceState.putParcelableArrayList(SAVED_NESTED_SCROLL_WEBVIEW_STATE_ARRAY_LIST, savedNestedScrollWebViewStateArrayList);
803 savedInstanceState.putInt(SAVED_TAB_POSITION, currentTabPosition);
804 savedInstanceState.putString(PROXY_MODE, proxyMode);
808 public void onDestroy() {
809 // Unregister the orbot status broadcast receiver if it exists.
810 if (orbotStatusBroadcastReceiver != null) {
811 this.unregisterReceiver(orbotStatusBroadcastReceiver);
814 // Close the bookmarks cursor if it exists.
815 if (bookmarksCursor != null) {
816 bookmarksCursor.close();
819 // Close the bookmarks database if it exists.
820 if (bookmarksDatabaseHelper != null) {
821 bookmarksDatabaseHelper.close();
824 // Stop populating the blocklists if the AsyncTask is running in the background.
825 if (populateBlocklists != null) {
826 populateBlocklists.cancel(true);
829 // Run the default commands.
834 public boolean onCreateOptionsMenu(Menu menu) {
835 // Inflate the menu. This adds items to the action bar if it is present.
836 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
838 // Store a handle for the options menu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons()`.
841 // Get handles for the class menu items.
842 optionsPrivacyMenuItem = menu.findItem(R.id.javascript);
843 optionsRefreshMenuItem = menu.findItem(R.id.refresh);
844 optionsCookiesMenuItem = menu.findItem(R.id.cookies);
845 optionsDomStorageMenuItem = menu.findItem(R.id.dom_storage);
846 optionsSaveFormDataMenuItem = menu.findItem(R.id.save_form_data); // Form data can be removed once the minimum API >= 26.
847 optionsClearDataMenuItem = menu.findItem(R.id.clear_data);
848 optionsClearCookiesMenuItem = menu.findItem(R.id.clear_cookies);
849 optionsClearDomStorageMenuItem = menu.findItem(R.id.clear_dom_storage);
850 optionsClearFormDataMenuItem = menu.findItem(R.id.clear_form_data); // Form data can be removed once the minimum API >= 26.
851 optionsBlocklistsMenuItem = menu.findItem(R.id.blocklists);
852 optionsEasyListMenuItem = menu.findItem(R.id.easylist);
853 optionsEasyPrivacyMenuItem = menu.findItem(R.id.easyprivacy);
854 optionsFanboysAnnoyanceListMenuItem = menu.findItem(R.id.fanboys_annoyance_list);
855 optionsFanboysSocialBlockingListMenuItem = menu.findItem(R.id.fanboys_social_blocking_list);
856 optionsUltraListMenuItem = menu.findItem(R.id.ultralist);
857 optionsUltraPrivacyMenuItem = menu.findItem(R.id.ultraprivacy);
858 optionsBlockAllThirdPartyRequestsMenuItem = menu.findItem(R.id.block_all_third_party_requests);
859 optionsProxyMenuItem = menu.findItem(R.id.proxy);
860 optionsProxyNoneMenuItem = menu.findItem(R.id.proxy_none);
861 optionsProxyTorMenuItem = menu.findItem(R.id.proxy_tor);
862 optionsProxyI2pMenuItem = menu.findItem(R.id.proxy_i2p);
863 optionsProxyCustomMenuItem = menu.findItem(R.id.proxy_custom);
864 optionsUserAgentMenuItem = menu.findItem(R.id.user_agent);
865 optionsUserAgentPrivacyBrowserMenuItem = menu.findItem(R.id.user_agent_privacy_browser);
866 optionsUserAgentWebViewDefaultMenuItem = menu.findItem(R.id.user_agent_webview_default);
867 optionsUserAgentFirefoxOnAndroidMenuItem = menu.findItem(R.id.user_agent_firefox_on_android);
868 optionsUserAgentChromeOnAndroidMenuItem = menu.findItem(R.id.user_agent_chrome_on_android);
869 optionsUserAgentSafariOnIosMenuItem = menu.findItem(R.id.user_agent_safari_on_ios);
870 optionsUserAgentFirefoxOnLinuxMenuItem = menu.findItem(R.id.user_agent_firefox_on_linux);
871 optionsUserAgentChromiumOnLinuxMenuItem = menu.findItem(R.id.user_agent_chromium_on_linux);
872 optionsUserAgentFirefoxOnWindowsMenuItem = menu.findItem(R.id.user_agent_firefox_on_windows);
873 optionsUserAgentChromeOnWindowsMenuItem = menu.findItem(R.id.user_agent_chrome_on_windows);
874 optionsUserAgentEdgeOnWindowsMenuItem = menu.findItem(R.id.user_agent_edge_on_windows);
875 optionsUserAgentInternetExplorerOnWindowsMenuItem = menu.findItem(R.id.user_agent_internet_explorer_on_windows);
876 optionsUserAgentSafariOnMacosMenuItem = menu.findItem(R.id.user_agent_safari_on_macos);
877 optionsUserAgentCustomMenuItem = menu.findItem(R.id.user_agent_custom);
878 optionsSwipeToRefreshMenuItem = menu.findItem(R.id.swipe_to_refresh);
879 optionsWideViewportMenuItem = menu.findItem(R.id.wide_viewport);
880 optionsDisplayImagesMenuItem = menu.findItem(R.id.display_images);
881 optionsDarkWebViewMenuItem = menu.findItem(R.id.dark_webview);
882 optionsFontSizeMenuItem = menu.findItem(R.id.font_size);
883 optionsAddOrEditDomainMenuItem = menu.findItem(R.id.add_or_edit_domain);
885 // Get handles for the method menu items.
886 MenuItem bookmarksMenuItem = menu.findItem(R.id.bookmarks);
887 MenuItem adConsentMenuItem = menu.findItem(R.id.ad_consent);
889 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
890 updatePrivacyIcons(false);
892 // Only display the form data menu items if the API < 26.
893 optionsSaveFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
894 optionsClearFormDataMenuItem.setVisible(Build.VERSION.SDK_INT < 26);
896 // Disable the clear form data menu item if the API >= 26 so that the status of the main Clear Data is calculated correctly.
897 optionsClearFormDataMenuItem.setEnabled(Build.VERSION.SDK_INT < 26);
899 // Only display the dark WebView menu item if API >= 21.
900 optionsDarkWebViewMenuItem.setVisible(Build.VERSION.SDK_INT >= 21);
902 // Only show Ad Consent if this is the free flavor.
903 adConsentMenuItem.setVisible(BuildConfig.FLAVOR.contentEquals("free"));
905 // Get the shared preferences.
906 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
908 // Get the dark theme and app bar preferences.
909 boolean displayAdditionalAppBarIcons = sharedPreferences.getBoolean(getString(R.string.display_additional_app_bar_icons_key), false);
911 // 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.
912 if (displayAdditionalAppBarIcons) {
913 optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
914 bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
915 optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
916 } else { //Do not display the additional icons.
917 optionsRefreshMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
918 bookmarksMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
919 optionsCookiesMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
922 // Replace Refresh with Stop if a URL is already loading.
923 if (currentWebView != null && currentWebView.getProgress() != 100) {
925 optionsRefreshMenuItem.setTitle(R.string.stop);
927 // Set the icon if it is displayed in the app bar.
928 if (displayAdditionalAppBarIcons) {
929 // Get the current theme status.
930 int currentThemeStatus = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
932 // Set the icon according to the current theme status.
933 if (currentThemeStatus == Configuration.UI_MODE_NIGHT_NO) {
934 optionsRefreshMenuItem.setIcon(R.drawable.close_blue_day);
936 optionsRefreshMenuItem.setIcon(R.drawable.close_blue_night);
946 public boolean onPrepareOptionsMenu(Menu menu) {
947 // Get a handle for the cookie manager.
948 CookieManager cookieManager = CookieManager.getInstance();
950 // Initialize the current user agent string and the font size.
951 String currentUserAgent = getString(R.string.user_agent_privacy_browser);
954 // 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.
955 if (currentWebView != null) {
956 // Set the add or edit domain text.
957 if (currentWebView.getDomainSettingsApplied()) {
958 optionsAddOrEditDomainMenuItem.setTitle(R.string.edit_domain_settings);
960 optionsAddOrEditDomainMenuItem.setTitle(R.string.add_domain_settings);
963 // Get the current user agent from the WebView.
964 currentUserAgent = currentWebView.getSettings().getUserAgentString();
966 // Get the current font size from the
967 fontSize = currentWebView.getSettings().getTextZoom();
969 // Set the status of the menu item checkboxes.
970 optionsDomStorageMenuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
971 optionsSaveFormDataMenuItem.setChecked(currentWebView.getSettings().getSaveFormData()); // Form data can be removed once the minimum API >= 26.
972 optionsEasyListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
973 optionsEasyPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
974 optionsFanboysAnnoyanceListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
975 optionsFanboysSocialBlockingListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
976 optionsUltraListMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
977 optionsUltraPrivacyMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
978 optionsBlockAllThirdPartyRequestsMenuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
979 optionsSwipeToRefreshMenuItem.setChecked(currentWebView.getSwipeToRefresh());
980 optionsWideViewportMenuItem.setChecked(currentWebView.getSettings().getUseWideViewPort());
981 optionsDisplayImagesMenuItem.setChecked(currentWebView.getSettings().getLoadsImagesAutomatically());
983 // Initialize the display names for the blocklists with the number of blocked requests.
984 optionsBlocklistsMenuItem.setTitle(getString(R.string.blocklists) + " - " + currentWebView.getRequestsCount(NestedScrollWebView.BLOCKED_REQUESTS));
985 optionsEasyListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYLIST) + " - " + getString(R.string.easylist));
986 optionsEasyPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.EASYPRIVACY) + " - " + getString(R.string.easyprivacy));
987 optionsFanboysAnnoyanceListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST) + " - " + getString(R.string.fanboys_annoyance_list));
988 optionsFanboysSocialBlockingListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST) + " - " + getString(R.string.fanboys_social_blocking_list));
989 optionsUltraListMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRALIST) + " - " + getString(R.string.ultralist));
990 optionsUltraPrivacyMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.ULTRAPRIVACY) + " - " + getString(R.string.ultraprivacy));
991 optionsBlockAllThirdPartyRequestsMenuItem.setTitle(currentWebView.getRequestsCount(NestedScrollWebView.THIRD_PARTY_REQUESTS) + " - " + getString(R.string.block_all_third_party_requests));
993 // Enable DOM Storage if JavaScript is enabled.
994 optionsDomStorageMenuItem.setEnabled(currentWebView.getSettings().getJavaScriptEnabled());
996 // Set the checkbox status for dark WebView if the WebView supports it.
997 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
998 optionsDarkWebViewMenuItem.setChecked(WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON);
1002 // Set the cookies menu item checked status.
1003 optionsCookiesMenuItem.setChecked(cookieManager.acceptCookie());
1005 // Enable Clear Cookies if there are any.
1006 optionsClearCookiesMenuItem.setEnabled(cookieManager.hasCookies());
1008 // 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`.
1009 String privateDataDirectoryString = getApplicationInfo().dataDir;
1011 // Get a count of the number of files in the Local Storage directory.
1012 File localStorageDirectory = new File (privateDataDirectoryString + "/app_webview/Local Storage/");
1013 int localStorageDirectoryNumberOfFiles = 0;
1014 if (localStorageDirectory.exists()) {
1015 // `Objects.requireNonNull` removes a lint warning that `localStorageDirectory.list` might produce a null pointed exception if it is dereferenced.
1016 localStorageDirectoryNumberOfFiles = Objects.requireNonNull(localStorageDirectory.list()).length;
1019 // Get a count of the number of files in the IndexedDB directory.
1020 File indexedDBDirectory = new File (privateDataDirectoryString + "/app_webview/IndexedDB");
1021 int indexedDBDirectoryNumberOfFiles = 0;
1022 if (indexedDBDirectory.exists()) {
1023 // `Objects.requireNonNull` removes a lint warning that `indexedDBDirectory.list` might produce a null pointed exception if it is dereferenced.
1024 indexedDBDirectoryNumberOfFiles = Objects.requireNonNull(indexedDBDirectory.list()).length;
1027 // Enable Clear DOM Storage if there is any.
1028 optionsClearDomStorageMenuItem.setEnabled(localStorageDirectoryNumberOfFiles > 0 || indexedDBDirectoryNumberOfFiles > 0);
1030 // Enable Clear Form Data is there is any. This can be removed once the minimum API >= 26.
1031 if (Build.VERSION.SDK_INT < 26) {
1032 // Get the WebView database.
1033 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1035 // Enable the clear form data menu item if there is anything to clear.
1036 optionsClearFormDataMenuItem.setEnabled(webViewDatabase.hasFormData());
1039 // Enable Clear Data if any of the submenu items are enabled.
1040 optionsClearDataMenuItem.setEnabled(optionsClearCookiesMenuItem.isEnabled() || optionsClearDomStorageMenuItem.isEnabled() || optionsClearFormDataMenuItem.isEnabled());
1042 // Disable Fanboy's Social Blocking List menu item if Fanboy's Annoyance List is checked.
1043 optionsFanboysSocialBlockingListMenuItem.setEnabled(!optionsFanboysAnnoyanceListMenuItem.isChecked());
1045 // Set the proxy title and check the applied proxy.
1046 switch (proxyMode) {
1047 case ProxyHelper.NONE:
1048 // Set the proxy title.
1049 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_none));
1051 // Check the proxy None radio button.
1052 optionsProxyNoneMenuItem.setChecked(true);
1055 case ProxyHelper.TOR:
1056 // Set the proxy title.
1057 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_tor));
1059 // Check the proxy Tor radio button.
1060 optionsProxyTorMenuItem.setChecked(true);
1063 case ProxyHelper.I2P:
1064 // Set the proxy title.
1065 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_i2p));
1067 // Check the proxy I2P radio button.
1068 optionsProxyI2pMenuItem.setChecked(true);
1071 case ProxyHelper.CUSTOM:
1072 // Set the proxy title.
1073 optionsProxyMenuItem.setTitle(getString(R.string.proxy) + " - " + getString(R.string.proxy_custom));
1075 // Check the proxy Custom radio button.
1076 optionsProxyCustomMenuItem.setChecked(true);
1080 // Select the current user agent menu item. A switch statement cannot be used because the user agents are not compile time constants.
1081 if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[0])) { // Privacy Browser.
1082 // Update the user agent menu item title.
1083 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_privacy_browser));
1085 // Select the Privacy Browser radio box.
1086 optionsUserAgentPrivacyBrowserMenuItem.setChecked(true);
1087 } else if (currentUserAgent.equals(webViewDefaultUserAgent)) { // WebView Default.
1088 // Update the user agent menu item title.
1089 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_webview_default));
1091 // Select the WebView Default radio box.
1092 optionsUserAgentWebViewDefaultMenuItem.setChecked(true);
1093 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[2])) { // Firefox on Android.
1094 // Update the user agent menu item title.
1095 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_android));
1097 // Select the Firefox on Android radio box.
1098 optionsUserAgentFirefoxOnAndroidMenuItem.setChecked(true);
1099 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[3])) { // Chrome on Android.
1100 // Update the user agent menu item title.
1101 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_android));
1103 // Select the Chrome on Android radio box.
1104 optionsUserAgentChromeOnAndroidMenuItem.setChecked(true);
1105 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[4])) { // Safari on iOS.
1106 // Update the user agent menu item title.
1107 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_ios));
1109 // Select the Safari on iOS radio box.
1110 optionsUserAgentSafariOnIosMenuItem.setChecked(true);
1111 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[5])) { // Firefox on Linux.
1112 // Update the user agent menu item title.
1113 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_linux));
1115 // Select the Firefox on Linux radio box.
1116 optionsUserAgentFirefoxOnLinuxMenuItem.setChecked(true);
1117 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[6])) { // Chromium on Linux.
1118 // Update the user agent menu item title.
1119 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chromium_on_linux));
1121 // Select the Chromium on Linux radio box.
1122 optionsUserAgentChromiumOnLinuxMenuItem.setChecked(true);
1123 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[7])) { // Firefox on Windows.
1124 // Update the user agent menu item title.
1125 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_firefox_on_windows));
1127 // Select the Firefox on Windows radio box.
1128 optionsUserAgentFirefoxOnWindowsMenuItem.setChecked(true);
1129 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[8])) { // Chrome on Windows.
1130 // Update the user agent menu item title.
1131 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_chrome_on_windows));
1133 // Select the Chrome on Windows radio box.
1134 optionsUserAgentChromeOnWindowsMenuItem.setChecked(true);
1135 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[9])) { // Edge on Windows.
1136 // Update the user agent menu item title.
1137 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_edge_on_windows));
1139 // Select the Edge on Windows radio box.
1140 optionsUserAgentEdgeOnWindowsMenuItem.setChecked(true);
1141 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[10])) { // Internet Explorer on Windows.
1142 // Update the user agent menu item title.
1143 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_internet_explorer_on_windows));
1145 // Select the Internet on Windows radio box.
1146 optionsUserAgentInternetExplorerOnWindowsMenuItem.setChecked(true);
1147 } else if (currentUserAgent.equals(getResources().getStringArray(R.array.user_agent_data)[11])) { // Safari on macOS.
1148 // Update the user agent menu item title.
1149 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_safari_on_macos));
1151 // Select the Safari on macOS radio box.
1152 optionsUserAgentSafariOnMacosMenuItem.setChecked(true);
1153 } else { // Custom user agent.
1154 // Update the user agent menu item title.
1155 optionsUserAgentMenuItem.setTitle(getString(R.string.options_user_agent) + " - " + getString(R.string.user_agent_custom));
1157 // Select the Custom radio box.
1158 optionsUserAgentCustomMenuItem.setChecked(true);
1161 // Set the font size title.
1162 optionsFontSizeMenuItem.setTitle(getString(R.string.font_size) + " - " + fontSize + "%");
1164 // Run all the other default commands.
1165 super.onPrepareOptionsMenu(menu);
1167 // Display the menu.
1172 public boolean onOptionsItemSelected(MenuItem menuItem) {
1173 // Get a handle for the shared preferences.
1174 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1176 // Get a handle for the cookie manager.
1177 CookieManager cookieManager = CookieManager.getInstance();
1179 // Get the selected menu item ID.
1180 int menuItemId = menuItem.getItemId();
1182 // Run the commands that correlate to the selected menu item.
1183 if (menuItemId == R.id.javascript) { // JavaScript.
1184 // Toggle the JavaScript status.
1185 currentWebView.getSettings().setJavaScriptEnabled(!currentWebView.getSettings().getJavaScriptEnabled());
1187 // Update the privacy icon.
1188 updatePrivacyIcons(true);
1190 // Display a `Snackbar`.
1191 if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScrip is enabled.
1192 Snackbar.make(webViewPager, R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
1193 } else if (cookieManager.acceptCookie()) { // JavaScript is disabled, but first-party cookies are enabled.
1194 Snackbar.make(webViewPager, R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
1195 } else { // Privacy mode.
1196 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1199 // Reload the current WebView.
1200 currentWebView.reload();
1202 // Consume the event.
1204 } else if (menuItemId == R.id.refresh) { // Refresh.
1205 // Run the command that correlates to the current status of the menu item.
1206 if (menuItem.getTitle().equals(getString(R.string.refresh))) { // The refresh button was pushed.
1207 // Reload the current WebView.
1208 currentWebView.reload();
1209 } else { // The stop button was pushed.
1210 // Stop the loading of the WebView.
1211 currentWebView.stopLoading();
1214 // Consume the event.
1216 } else if (menuItemId == R.id.bookmarks) { // Bookmarks.
1217 // Open the bookmarks drawer.
1218 drawerLayout.openDrawer(GravityCompat.END);
1220 // Consume the event.
1222 } else if (menuItemId == R.id.cookies) { // Cookies.
1223 // Switch the first-party cookie status.
1224 cookieManager.setAcceptCookie(!cookieManager.acceptCookie());
1226 // Store the cookie status.
1227 currentWebView.setAcceptCookies(cookieManager.acceptCookie());
1229 // Update the menu checkbox.
1230 menuItem.setChecked(cookieManager.acceptCookie());
1232 // Update the privacy icon.
1233 updatePrivacyIcons(true);
1235 // Display a snackbar.
1236 if (cookieManager.acceptCookie()) { // Cookies are enabled.
1237 Snackbar.make(webViewPager, R.string.cookies_enabled, Snackbar.LENGTH_SHORT).show();
1238 } else if (currentWebView.getSettings().getJavaScriptEnabled()) { // JavaScript is still enabled.
1239 Snackbar.make(webViewPager, R.string.cookies_disabled, Snackbar.LENGTH_SHORT).show();
1240 } else { // Privacy mode.
1241 Snackbar.make(webViewPager, R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
1244 // Reload the current WebView.
1245 currentWebView.reload();
1247 // Consume the event.
1249 } else if (menuItemId == R.id.dom_storage) { // DOM storage.
1250 // Toggle the status of domStorageEnabled.
1251 currentWebView.getSettings().setDomStorageEnabled(!currentWebView.getSettings().getDomStorageEnabled());
1253 // Update the menu checkbox.
1254 menuItem.setChecked(currentWebView.getSettings().getDomStorageEnabled());
1256 // Update the privacy icon.
1257 updatePrivacyIcons(true);
1259 // Display a snackbar.
1260 if (currentWebView.getSettings().getDomStorageEnabled()) {
1261 Snackbar.make(webViewPager, R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
1263 Snackbar.make(webViewPager, R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
1266 // Reload the current WebView.
1267 currentWebView.reload();
1269 // Consume the event.
1271 } else if (menuItemId == R.id.save_form_data) { // Form data. This can be removed once the minimum API >= 26.
1272 // Switch the status of saveFormDataEnabled.
1273 currentWebView.getSettings().setSaveFormData(!currentWebView.getSettings().getSaveFormData());
1275 // Update the menu checkbox.
1276 menuItem.setChecked(currentWebView.getSettings().getSaveFormData());
1278 // Display a snackbar.
1279 if (currentWebView.getSettings().getSaveFormData()) {
1280 Snackbar.make(webViewPager, R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
1282 Snackbar.make(webViewPager, R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
1285 // Update the privacy icon.
1286 updatePrivacyIcons(true);
1288 // Reload the current WebView.
1289 currentWebView.reload();
1291 // Consume the event.
1293 } else if (menuItemId == R.id.clear_cookies) { // Clear cookies.
1294 // Create a snackbar.
1295 Snackbar.make(webViewPager, R.string.cookies_deleted, Snackbar.LENGTH_LONG)
1296 .setAction(R.string.undo, v -> {
1297 // Do nothing because everything will be handled by `onDismissed()` below.
1299 .addCallback(new Snackbar.Callback() {
1301 public void onDismissed(Snackbar snackbar, int event) {
1302 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1303 // Delete the cookies, which command varies by SDK.
1304 if (Build.VERSION.SDK_INT < 21) {
1305 cookieManager.removeAllCookie();
1307 cookieManager.removeAllCookies(null);
1314 // Consume the event.
1316 } else if (menuItemId == R.id.clear_dom_storage) { // Clear DOM storage.
1317 // Create a snackbar.
1318 Snackbar.make(webViewPager, R.string.dom_storage_deleted, Snackbar.LENGTH_LONG)
1319 .setAction(R.string.undo, v -> {
1320 // Do nothing because everything will be handled by `onDismissed()` below.
1322 .addCallback(new Snackbar.Callback() {
1324 public void onDismissed(Snackbar snackbar, int event) {
1325 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1326 // Delete the DOM Storage.
1327 WebStorage webStorage = WebStorage.getInstance();
1328 webStorage.deleteAllData();
1330 // Initialize a handler to manually delete the DOM storage files and directories.
1331 Handler deleteDomStorageHandler = new Handler();
1333 // Setup a runnable to manually delete the DOM storage files and directories.
1334 Runnable deleteDomStorageRunnable = () -> {
1336 // Get a handle for the runtime.
1337 Runtime runtime = Runtime.getRuntime();
1339 // Get the application's private data directory, which will be something like `/data/user/0/com.stoutner.privacybrowser.standard`,
1340 // which links to `/data/data/com.stoutner.privacybrowser.standard`.
1341 String privateDataDirectoryString = getApplicationInfo().dataDir;
1343 // A string array must be used because the directory contains a space and `Runtime.exec` will otherwise not escape the string correctly.
1344 Process deleteLocalStorageProcess = runtime.exec(new String[]{"rm", "-rf", privateDataDirectoryString + "/app_webview/Local Storage/"});
1346 // Multiple commands must be used because `Runtime.exec()` does not like `*`.
1347 Process deleteIndexProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/IndexedDB");
1348 Process deleteQuotaManagerProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager");
1349 Process deleteQuotaManagerJournalProcess = runtime.exec("rm -f " + privateDataDirectoryString + "/app_webview/QuotaManager-journal");
1350 Process deleteDatabasesProcess = runtime.exec("rm -rf " + privateDataDirectoryString + "/app_webview/databases");
1352 // Wait for the processes to finish.
1353 deleteLocalStorageProcess.waitFor();
1354 deleteIndexProcess.waitFor();
1355 deleteQuotaManagerProcess.waitFor();
1356 deleteQuotaManagerJournalProcess.waitFor();
1357 deleteDatabasesProcess.waitFor();
1358 } catch (Exception exception) {
1359 // Do nothing if an error is thrown.
1363 // Manually delete the DOM storage files after 200 milliseconds.
1364 deleteDomStorageHandler.postDelayed(deleteDomStorageRunnable, 200);
1370 // Consume the event.
1372 } else if (menuItemId == R.id.clear_form_data) { // Clear form data. This can be remove once the minimum API >= 26.
1373 // Create a snackbar.
1374 Snackbar.make(webViewPager, R.string.form_data_deleted, Snackbar.LENGTH_LONG)
1375 .setAction(R.string.undo, v -> {
1376 // Do nothing because everything will be handled by `onDismissed()` below.
1378 .addCallback(new Snackbar.Callback() {
1380 public void onDismissed(Snackbar snackbar, int event) {
1381 if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { // The snackbar was dismissed without the undo button being pushed.
1382 // Get a handle for the webView database.
1383 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(getApplicationContext());
1385 // Delete the form data.
1386 webViewDatabase.clearFormData();
1392 // Consume the event.
1394 } else if (menuItemId == R.id.easylist) { // EasyList.
1395 // Toggle the EasyList status.
1396 currentWebView.enableBlocklist(NestedScrollWebView.EASYLIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1398 // Update the menu checkbox.
1399 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYLIST));
1401 // Reload the current WebView.
1402 currentWebView.reload();
1404 // Consume the event.
1406 } else if (menuItemId == R.id.easyprivacy) { // EasyPrivacy.
1407 // Toggle the EasyPrivacy status.
1408 currentWebView.enableBlocklist(NestedScrollWebView.EASYPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1410 // Update the menu checkbox.
1411 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.EASYPRIVACY));
1413 // Reload the current WebView.
1414 currentWebView.reload();
1416 // Consume the event.
1418 } else if (menuItemId == R.id.fanboys_annoyance_list) { // Fanboy's Annoyance List.
1419 // Toggle Fanboy's Annoyance List status.
1420 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1422 // Update the menu checkbox.
1423 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1425 // Update the status of Fanboy's Social Blocking List.
1426 optionsFanboysSocialBlockingListMenuItem.setEnabled(!currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_ANNOYANCE_LIST));
1428 // Reload the current WebView.
1429 currentWebView.reload();
1431 // Consume the event.
1433 } else if (menuItemId == R.id.fanboys_social_blocking_list) { // Fanboy's Social Blocking List.
1434 // Toggle Fanboy's Social Blocking List status.
1435 currentWebView.enableBlocklist(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1437 // Update the menu checkbox.
1438 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.FANBOYS_SOCIAL_BLOCKING_LIST));
1440 // Reload the current WebView.
1441 currentWebView.reload();
1443 // Consume the event.
1445 } else if (menuItemId == R.id.ultralist) { // UltraList.
1446 // Toggle the UltraList status.
1447 currentWebView.enableBlocklist(NestedScrollWebView.ULTRALIST, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1449 // Update the menu checkbox.
1450 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRALIST));
1452 // Reload the current WebView.
1453 currentWebView.reload();
1455 // Consume the event.
1457 } else if (menuItemId == R.id.ultraprivacy) { // UltraPrivacy.
1458 // Toggle the UltraPrivacy status.
1459 currentWebView.enableBlocklist(NestedScrollWebView.ULTRAPRIVACY, !currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1461 // Update the menu checkbox.
1462 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.ULTRAPRIVACY));
1464 // Reload the current WebView.
1465 currentWebView.reload();
1467 // Consume the event.
1469 } else if (menuItemId == R.id.block_all_third_party_requests) { // Block all third-party requests.
1470 //Toggle the third-party requests blocker status.
1471 currentWebView.enableBlocklist(NestedScrollWebView.THIRD_PARTY_REQUESTS, !currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1473 // Update the menu checkbox.
1474 menuItem.setChecked(currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
1476 // Reload the current WebView.
1477 currentWebView.reload();
1479 // Consume the event.
1481 } else if (menuItemId == R.id.proxy_none) { // Proxy - None.
1482 // Update the proxy mode.
1483 proxyMode = ProxyHelper.NONE;
1485 // Apply the proxy mode.
1488 // Consume the event.
1490 } else if (menuItemId == R.id.proxy_tor) { // Proxy - Tor.
1491 // Update the proxy mode.
1492 proxyMode = ProxyHelper.TOR;
1494 // Apply the proxy mode.
1497 // Consume the event.
1499 } else if (menuItemId == R.id.proxy_i2p) { // Proxy - I2P.
1500 // Update the proxy mode.
1501 proxyMode = ProxyHelper.I2P;
1503 // Apply the proxy mode.
1506 // Consume the event.
1508 } else if (menuItemId == R.id.proxy_custom) { // Proxy - Custom.
1509 // Update the proxy mode.
1510 proxyMode = ProxyHelper.CUSTOM;
1512 // Apply the proxy mode.
1515 // Consume the event.
1517 } else if (menuItemId == R.id.user_agent_privacy_browser) { // User Agent - Privacy Browser.
1518 // Update the user agent.
1519 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[0]);
1521 // Reload the current WebView.
1522 currentWebView.reload();
1524 // Consume the event.
1526 } else if (menuItemId == R.id.user_agent_webview_default) { // User Agent - WebView Default.
1527 // Update the user agent.
1528 currentWebView.getSettings().setUserAgentString("");
1530 // Reload the current WebView.
1531 currentWebView.reload();
1533 // Consume the event.
1535 } else if (menuItemId == R.id.user_agent_firefox_on_android) { // User Agent - Firefox on Android.
1536 // Update the user agent.
1537 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[2]);
1539 // Reload the current WebView.
1540 currentWebView.reload();
1542 // Consume the event.
1544 } else if (menuItemId == R.id.user_agent_chrome_on_android) { // User Agent - Chrome on Android.
1545 // Update the user agent.
1546 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[3]);
1548 // Reload the current WebView.
1549 currentWebView.reload();
1551 // Consume the event.
1553 } else if (menuItemId == R.id.user_agent_safari_on_ios) { // User Agent - Safari on iOS.
1554 // Update the user agent.
1555 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[4]);
1557 // Reload the current WebView.
1558 currentWebView.reload();
1560 // Consume the event.
1562 } else if (menuItemId == R.id.user_agent_firefox_on_linux) { // User Agent - Firefox on Linux.
1563 // Update the user agent.
1564 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[5]);
1566 // Reload the current WebView.
1567 currentWebView.reload();
1569 // Consume the event.
1571 } else if (menuItemId == R.id.user_agent_chromium_on_linux) { // User Agent - Chromium on Linux.
1572 // Update the user agent.
1573 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[6]);
1575 // Reload the current WebView.
1576 currentWebView.reload();
1578 // Consume the event.
1580 } else if (menuItemId == R.id.user_agent_firefox_on_windows) { // User Agent - Firefox on Windows.
1581 // Update the user agent.
1582 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[7]);
1584 // Reload the current WebView.
1585 currentWebView.reload();
1587 // Consume the event.
1589 } else if (menuItemId == R.id.user_agent_chrome_on_windows) { // User Agent - Chrome on Windows.
1590 // Update the user agent.
1591 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[8]);
1593 // Reload the current WebView.
1594 currentWebView.reload();
1596 // Consume the event.
1598 } else if (menuItemId == R.id.user_agent_edge_on_windows) { // User Agent - Edge on Windows.
1599 // Update the user agent.
1600 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[9]);
1602 // Reload the current WebView.
1603 currentWebView.reload();
1605 // Consume the event.
1607 } else if (menuItemId == R.id.user_agent_internet_explorer_on_windows) { // User Agent - Internet Explorer on Windows.
1608 // Update the user agent.
1609 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[10]);
1611 // Reload the current WebView.
1612 currentWebView.reload();
1614 // Consume the event.
1616 } else if (menuItemId == R.id.user_agent_safari_on_macos) { // User Agent - Safari on macOS.
1617 // Update the user agent.
1618 currentWebView.getSettings().setUserAgentString(getResources().getStringArray(R.array.user_agent_data)[11]);
1620 // Reload the current WebView.
1621 currentWebView.reload();
1623 // Consume the event.
1625 } else if (menuItemId == R.id.user_agent_custom) { // User Agent - Custom.
1626 // Update the user agent.
1627 currentWebView.getSettings().setUserAgentString(sharedPreferences.getString("custom_user_agent", getString(R.string.custom_user_agent_default_value)));
1629 // Reload the current WebView.
1630 currentWebView.reload();
1632 // Consume the event.
1634 } else if (menuItemId == R.id.font_size) { // Font size.
1635 // Instantiate the font size dialog.
1636 DialogFragment fontSizeDialogFragment = FontSizeDialog.displayDialog(currentWebView.getSettings().getTextZoom());
1638 // Show the font size dialog.
1639 fontSizeDialogFragment.show(getSupportFragmentManager(), getString(R.string.font_size));
1641 // Consume the event.
1643 } else if (menuItemId == R.id.swipe_to_refresh) { // Swipe to refresh.
1644 // Toggle the stored status of swipe to refresh.
1645 currentWebView.setSwipeToRefresh(!currentWebView.getSwipeToRefresh());
1647 // Update the swipe refresh layout.
1648 if (currentWebView.getSwipeToRefresh()) { // Swipe to refresh is enabled.
1649 // Only enable the swipe refresh layout if the WebView is scrolled to the top. It is updated every time the scroll changes.
1650 swipeRefreshLayout.setEnabled(currentWebView.getScrollY() == 0);
1651 } else { // Swipe to refresh is disabled.
1652 // Disable the swipe refresh layout.
1653 swipeRefreshLayout.setEnabled(false);
1656 // Consume the event.
1658 } else if (menuItemId == R.id.wide_viewport) { // Wide viewport.
1659 // Toggle the viewport.
1660 currentWebView.getSettings().setUseWideViewPort(!currentWebView.getSettings().getUseWideViewPort());
1662 // Consume the event.
1664 } else if (menuItemId == R.id.display_images) { // Display images.
1665 // Toggle the displaying of images.
1666 if (currentWebView.getSettings().getLoadsImagesAutomatically()) { // Images are currently loaded automatically.
1667 // Disable loading of images.
1668 currentWebView.getSettings().setLoadsImagesAutomatically(false);
1670 // Reload the website to remove existing images.
1671 currentWebView.reload();
1672 } else { // Images are not currently loaded automatically.
1673 // Enable loading of images. Missing images will be loaded without the need for a reload.
1674 currentWebView.getSettings().setLoadsImagesAutomatically(true);
1677 // Consume the event.
1679 } else if (menuItemId == R.id.dark_webview) { // Dark WebView.
1680 // Check to see if dark WebView is supported by this WebView.
1681 if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {
1682 // Toggle the dark WebView setting.
1683 if (WebSettingsCompat.getForceDark(currentWebView.getSettings()) == WebSettingsCompat.FORCE_DARK_ON) { // Dark WebView is currently enabled.
1684 // Turn off dark WebView.
1685 WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_OFF);
1686 } else { // Dark WebView is currently disabled.
1687 // Turn on dark WebView.
1688 WebSettingsCompat.setForceDark(currentWebView.getSettings(), WebSettingsCompat.FORCE_DARK_ON);
1692 // Consume the event.
1694 } else if (menuItemId == R.id.find_on_page) { // Find on page.
1695 // Get a handle for the views.
1696 Toolbar toolbar = findViewById(R.id.toolbar);
1697 LinearLayout findOnPageLinearLayout = findViewById(R.id.find_on_page_linearlayout);
1698 EditText findOnPageEditText = findViewById(R.id.find_on_page_edittext);
1700 // Set the minimum height of the find on page linear layout to match the toolbar.
1701 findOnPageLinearLayout.setMinimumHeight(toolbar.getHeight());
1703 // Hide the toolbar.
1704 toolbar.setVisibility(View.GONE);
1706 // Show the find on page linear layout.
1707 findOnPageLinearLayout.setVisibility(View.VISIBLE);
1709 // Display the keyboard. The app must wait 200 ms before running the command to work around a bug in Android.
1710 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
1711 findOnPageEditText.postDelayed(() -> {
1712 // Set the focus on the find on page edit text.
1713 findOnPageEditText.requestFocus();
1715 // Get a handle for the input method manager.
1716 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
1718 // Remove the lint warning below that the input method manager might be null.
1719 assert inputMethodManager != null;
1721 // Display the keyboard. `0` sets no input flags.
1722 inputMethodManager.showSoftInput(findOnPageEditText, 0);
1725 // Consume the event.
1727 } else if (menuItemId == R.id.print) { // Print.
1728 // Get a print manager instance.
1729 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1731 // Remove the lint error below that print manager might be null.
1732 assert printManager != null;
1734 // Create a print document adapter from the current WebView.
1735 PrintDocumentAdapter printDocumentAdapter = currentWebView.createPrintDocumentAdapter();
1737 // Print the document.
1738 printManager.print(getString(R.string.privacy_browser_webpage), printDocumentAdapter, null);
1740 // Consume the event.
1742 } else if (menuItemId == R.id.save_url) { // Save URL.
1743 // Check the download preference.
1744 if (downloadWithExternalApp) { // Download with an external app.
1745 downloadUrlWithExternalApp(currentWebView.getCurrentUrl());
1746 } else { // Handle the download inside of Privacy Browser.
1747 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
1748 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
1749 currentWebView.getAcceptCookies()).execute(currentWebView.getCurrentUrl());
1752 // Consume the event.
1754 } else if (menuItemId == R.id.save_archive) {
1755 // Instantiate the save dialog.
1756 DialogFragment saveArchiveFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_ARCHIVE, currentWebView.getCurrentUrl(), null, null, null,
1759 // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
1760 saveArchiveFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1763 } else if (menuItemId == R.id.save_image) { // Save image.
1764 // Instantiate the save dialog.
1765 DialogFragment saveImageFragment = SaveWebpageDialog.saveWebpage(SaveWebpageDialog.SAVE_IMAGE, currentWebView.getCurrentUrl(), null, null, null,
1768 // Show the save dialog. It must be named `save_dialog` so that the file picker can update the file name.
1769 saveImageFragment.show(getSupportFragmentManager(), getString(R.string.save_dialog));
1771 // Consume the event.
1773 } else if (menuItemId == R.id.add_to_homescreen) { // Add to homescreen.
1774 // Instantiate the create home screen shortcut dialog.
1775 DialogFragment createHomeScreenShortcutDialogFragment = CreateHomeScreenShortcutDialog.createDialog(currentWebView.getTitle(), currentWebView.getUrl(),
1776 currentWebView.getFavoriteOrDefaultIcon());
1778 // Show the create home screen shortcut dialog.
1779 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getString(R.string.create_shortcut));
1781 // Consume the event.
1783 } else if (menuItemId == R.id.view_source) { // View source.
1784 // Create an intent to launch the view source activity.
1785 Intent viewSourceIntent = new Intent(this, ViewSourceActivity.class);
1787 // Add the variables to the intent.
1788 viewSourceIntent.putExtra(ViewSourceActivityKt.CURRENT_URL, currentWebView.getUrl());
1789 viewSourceIntent.putExtra(ViewSourceActivityKt.USER_AGENT, currentWebView.getSettings().getUserAgentString());
1792 startActivity(viewSourceIntent);
1794 // Consume the event.
1796 } else if (menuItemId == R.id.share_url) { // Share URL.
1797 // Setup the share string.
1798 String shareString = currentWebView.getTitle() + " – " + currentWebView.getUrl();
1800 // Create the share intent.
1801 Intent shareIntent = new Intent(Intent.ACTION_SEND);
1803 // Add the share string to the intent.
1804 shareIntent.putExtra(Intent.EXTRA_TEXT, shareString);
1806 // Set the MIME type.
1807 shareIntent.setType("text/plain");
1809 // Set the intent to open in a new task.
1810 shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1813 startActivity(Intent.createChooser(shareIntent, getString(R.string.share_url)));
1815 // Consume the event.
1817 } else if (menuItemId == R.id.open_with_app) { // Open with app.
1818 // Open the URL with an outside app.
1819 openWithApp(currentWebView.getUrl());
1821 // Consume the event.
1823 } else if (menuItemId == R.id.open_with_browser) { // Open with browser.
1824 // Open the URL with an outside browser.
1825 openWithBrowser(currentWebView.getUrl());
1827 // Consume the event.
1829 } else if (menuItemId == R.id.add_or_edit_domain) { // Add or edit domain.
1830 // Check if domain settings currently exist.
1831 if (currentWebView.getDomainSettingsApplied()) { // Edit the current domain settings.
1832 // Reapply the domain settings on returning to `MainWebViewActivity`.
1833 reapplyDomainSettingsOnRestart = true;
1835 // Create an intent to launch the domains activity.
1836 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1838 // Add the extra information to the intent.
1839 domainsIntent.putExtra("load_domain", currentWebView.getDomainSettingsDatabaseId());
1840 domainsIntent.putExtra("close_on_back", true);
1841 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1843 // Get the current certificate.
1844 SslCertificate sslCertificate = currentWebView.getCertificate();
1846 // Check to see if the SSL certificate is populated.
1847 if (sslCertificate != null) {
1848 // Extract the certificate to strings.
1849 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1850 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1851 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1852 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1853 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1854 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1855 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1856 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1858 // Add the certificate to the intent.
1859 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1860 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1861 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1862 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1863 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1864 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1865 domainsIntent.putExtra("ssl_start_date", startDateLong);
1866 domainsIntent.putExtra("ssl_end_date", endDateLong);
1869 // Check to see if the current IP addresses have been received.
1870 if (currentWebView.hasCurrentIpAddresses()) {
1871 // Add the current IP addresses to the intent.
1872 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1876 startActivity(domainsIntent);
1877 } else { // Add a new domain.
1878 // Apply the new domain settings on returning to `MainWebViewActivity`.
1879 reapplyDomainSettingsOnRestart = true;
1881 // Get the current domain
1882 Uri currentUri = Uri.parse(currentWebView.getUrl());
1883 String currentDomain = currentUri.getHost();
1885 // Initialize the database handler. The `0` specifies the database version, but that is ignored and set instead using a constant in `DomainsDatabaseHelper`.
1886 DomainsDatabaseHelper domainsDatabaseHelper = new DomainsDatabaseHelper(this, null, null, 0);
1888 // Create the domain and store the database ID.
1889 int newDomainDatabaseId = domainsDatabaseHelper.addDomain(currentDomain);
1891 // Create an intent to launch the domains activity.
1892 Intent domainsIntent = new Intent(this, DomainsActivity.class);
1894 // Add the extra information to the intent.
1895 domainsIntent.putExtra("load_domain", newDomainDatabaseId);
1896 domainsIntent.putExtra("close_on_back", true);
1897 domainsIntent.putExtra("current_url", currentWebView.getUrl());
1899 // Get the current certificate.
1900 SslCertificate sslCertificate = currentWebView.getCertificate();
1902 // Check to see if the SSL certificate is populated.
1903 if (sslCertificate != null) {
1904 // Extract the certificate to strings.
1905 String issuedToCName = sslCertificate.getIssuedTo().getCName();
1906 String issuedToOName = sslCertificate.getIssuedTo().getOName();
1907 String issuedToUName = sslCertificate.getIssuedTo().getUName();
1908 String issuedByCName = sslCertificate.getIssuedBy().getCName();
1909 String issuedByOName = sslCertificate.getIssuedBy().getOName();
1910 String issuedByUName = sslCertificate.getIssuedBy().getUName();
1911 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
1912 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
1914 // Add the certificate to the intent.
1915 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
1916 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
1917 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
1918 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
1919 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
1920 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
1921 domainsIntent.putExtra("ssl_start_date", startDateLong);
1922 domainsIntent.putExtra("ssl_end_date", endDateLong);
1925 // Check to see if the current IP addresses have been received.
1926 if (currentWebView.hasCurrentIpAddresses()) {
1927 // Add the current IP addresses to the intent.
1928 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
1932 startActivity(domainsIntent);
1935 // Consume the event.
1937 } else if (menuItemId == R.id.ad_consent) { // Ad consent.
1938 // Instantiate the ad consent dialog.
1939 DialogFragment adConsentDialogFragment = new AdConsentDialog();
1941 // Display the ad consent dialog.
1942 adConsentDialogFragment.show(getSupportFragmentManager(), getString(R.string.ad_consent));
1944 // Consume the event.
1946 } else { // There is no match with the options menu. Pass the event up to the parent method.
1947 // Don't consume the event.
1948 return super.onOptionsItemSelected(menuItem);
1952 // removeAllCookies is deprecated, but it is required for API < 21.
1954 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1955 // Get a handle for the shared preferences.
1956 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1958 // Get the menu item ID.
1959 int menuItemId = menuItem.getItemId();
1961 // Run the commands that correspond to the selected menu item.
1962 if (menuItemId == R.id.clear_and_exit) { // Clear and exit.
1963 // Clear and exit Privacy Browser.
1965 } else if (menuItemId == R.id.home) { // Home.
1966 // Load the homepage.
1967 loadUrl(currentWebView, sharedPreferences.getString("homepage", getString(R.string.homepage_default_value)));
1968 } else if (menuItemId == R.id.back) { // Back.
1969 // Check if the WebView can go back.
1970 if (currentWebView.canGoBack()) {
1971 // Get the current web back forward list.
1972 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1974 // Get the previous entry URL.
1975 String previousUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() - 1).getUrl();
1977 // Apply the domain settings.
1978 applyDomainSettings(currentWebView, previousUrl, false, false, false);
1980 // Load the previous website in the history.
1981 currentWebView.goBack();
1983 } else if (menuItemId == R.id.forward) { // Forward.
1984 // Check if the WebView can go forward.
1985 if (currentWebView.canGoForward()) {
1986 // Get the current web back forward list.
1987 WebBackForwardList webBackForwardList = currentWebView.copyBackForwardList();
1989 // Get the next entry URL.
1990 String nextUrl = webBackForwardList.getItemAtIndex(webBackForwardList.getCurrentIndex() + 1).getUrl();
1992 // Apply the domain settings.
1993 applyDomainSettings(currentWebView, nextUrl, false, false, false);
1995 // Load the next website in the history.
1996 currentWebView.goForward();
1998 } else if (menuItemId == R.id.history) { // History.
1999 // Instantiate the URL history dialog.
2000 DialogFragment urlHistoryDialogFragment = UrlHistoryDialog.loadBackForwardList(currentWebView.getWebViewFragmentId());
2002 // Show the URL history dialog.
2003 urlHistoryDialogFragment.show(getSupportFragmentManager(), getString(R.string.history));
2004 } else if (menuItemId == R.id.open) { // Open.
2005 // Instantiate the open file dialog.
2006 DialogFragment openDialogFragment = new OpenDialog();
2008 // Show the open file dialog.
2009 openDialogFragment.show(getSupportFragmentManager(), getString(R.string.open));
2010 } else if (menuItemId == R.id.requests) { // Requests.
2011 // Populate the resource requests.
2012 RequestsActivity.resourceRequests = currentWebView.getResourceRequests();
2014 // Create an intent to launch the Requests activity.
2015 Intent requestsIntent = new Intent(this, RequestsActivity.class);
2017 // Add the block third-party requests status to the intent.
2018 requestsIntent.putExtra("block_all_third_party_requests", currentWebView.isBlocklistEnabled(NestedScrollWebView.THIRD_PARTY_REQUESTS));
2021 startActivity(requestsIntent);
2022 } else if (menuItemId == R.id.downloads) { // Downloads.
2023 // Try the default system download manager.
2025 // Launch the default system Download Manager.
2026 Intent defaultDownloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
2028 // Launch as a new task so that the download manager and Privacy Browser show as separate windows in the recent tasks list.
2029 defaultDownloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2032 startActivity(defaultDownloadManagerIntent);
2033 } catch (Exception defaultDownloadManagerException) {
2034 // Try a generic file manager.
2036 // Create a generic file manager intent.
2037 Intent genericFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2039 // Open the download directory.
2040 genericFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), DocumentsContract.Document.MIME_TYPE_DIR);
2042 // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2043 genericFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2046 startActivity(genericFileManagerIntent);
2047 } catch (Exception genericFileManagerException) {
2048 // Try an alternate file manager.
2050 // Create an alternate file manager intent.
2051 Intent alternateFileManagerIntent = new Intent(Intent.ACTION_VIEW);
2053 // Open the download directory.
2054 alternateFileManagerIntent.setDataAndType(Uri.parse(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()), "resource/folder");
2056 // Launch as a new task so that the file manager and Privacy Browser show as separate windows in the recent tasks list.
2057 alternateFileManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2059 // Open the alternate file manager.
2060 startActivity(alternateFileManagerIntent);
2061 } catch (Exception alternateFileManagerException) {
2062 // Display a snackbar.
2063 Snackbar.make(currentWebView, R.string.no_file_manager_detected, Snackbar.LENGTH_INDEFINITE).show();
2067 } else if (menuItemId == R.id.domains) { // Domains.
2068 // Set the flag to reapply the domain settings on restart when returning from Domain Settings.
2069 reapplyDomainSettingsOnRestart = true;
2071 // Launch the domains activity.
2072 Intent domainsIntent = new Intent(this, DomainsActivity.class);
2074 // Add the extra information to the intent.
2075 domainsIntent.putExtra("current_url", currentWebView.getUrl());
2077 // Get the current certificate.
2078 SslCertificate sslCertificate = currentWebView.getCertificate();
2080 // Check to see if the SSL certificate is populated.
2081 if (sslCertificate != null) {
2082 // Extract the certificate to strings.
2083 String issuedToCName = sslCertificate.getIssuedTo().getCName();
2084 String issuedToOName = sslCertificate.getIssuedTo().getOName();
2085 String issuedToUName = sslCertificate.getIssuedTo().getUName();
2086 String issuedByCName = sslCertificate.getIssuedBy().getCName();
2087 String issuedByOName = sslCertificate.getIssuedBy().getOName();
2088 String issuedByUName = sslCertificate.getIssuedBy().getUName();
2089 long startDateLong = sslCertificate.getValidNotBeforeDate().getTime();
2090 long endDateLong = sslCertificate.getValidNotAfterDate().getTime();
2092 // Add the certificate to the intent.
2093 domainsIntent.putExtra("ssl_issued_to_cname", issuedToCName);
2094 domainsIntent.putExtra("ssl_issued_to_oname", issuedToOName);
2095 domainsIntent.putExtra("ssl_issued_to_uname", issuedToUName);
2096 domainsIntent.putExtra("ssl_issued_by_cname", issuedByCName);
2097 domainsIntent.putExtra("ssl_issued_by_oname", issuedByOName);
2098 domainsIntent.putExtra("ssl_issued_by_uname", issuedByUName);
2099 domainsIntent.putExtra("ssl_start_date", startDateLong);
2100 domainsIntent.putExtra("ssl_end_date", endDateLong);
2103 // Check to see if the current IP addresses have been received.
2104 if (currentWebView.hasCurrentIpAddresses()) {
2105 // Add the current IP addresses to the intent.
2106 domainsIntent.putExtra("current_ip_addresses", currentWebView.getCurrentIpAddresses());
2110 startActivity(domainsIntent);
2111 } else if (menuItemId == R.id.settings) { // Settings.
2112 // Set the flag to reapply app settings on restart when returning from Settings.
2113 reapplyAppSettingsOnRestart = true;
2115 // Set the flag to reapply the domain settings on restart when returning from Settings.
2116 reapplyDomainSettingsOnRestart = true;
2118 // Launch the settings activity.
2119 Intent settingsIntent = new Intent(this, SettingsActivity.class);
2120 startActivity(settingsIntent);
2121 } else if (menuItemId == R.id.import_export) { // Import/Export.
2122 // Create an intent to launch the import/export activity.
2123 Intent importExportIntent = new Intent(this, ImportExportActivity.class);
2126 startActivity(importExportIntent);
2127 } else if (menuItemId == R.id.logcat) { // Logcat.
2128 // Create an intent to launch the logcat activity.
2129 Intent logcatIntent = new Intent(this, LogcatActivity.class);
2132 startActivity(logcatIntent);
2133 } else if (menuItemId == R.id.guide) { // Guide.
2134 // Create an intent to launch the guide activity.
2135 Intent guideIntent = new Intent(this, GuideActivity.class);
2138 startActivity(guideIntent);
2139 } else if (menuItemId == R.id.about) { // About
2140 // Create an intent to launch the about activity.
2141 Intent aboutIntent = new Intent(this, AboutActivity.class);
2143 // Create a string array for the blocklist versions.
2144 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],
2145 ultraList.get(0).get(0)[0], ultraPrivacy.get(0).get(0)[0]};
2147 // Add the blocklist versions to the intent.
2148 aboutIntent.putExtra(AboutActivity.BLOCKLIST_VERSIONS, blocklistVersions);
2151 startActivity(aboutIntent);
2154 // Close the navigation drawer.
2155 drawerLayout.closeDrawer(GravityCompat.START);
2160 public void onPostCreate(Bundle savedInstanceState) {
2161 // Run the default commands.
2162 super.onPostCreate(savedInstanceState);
2164 // Sync the state of the DrawerToggle after the default `onRestoreInstanceState()` has finished. This creates the navigation drawer icon.
2165 actionBarDrawerToggle.syncState();
2169 public void onConfigurationChanged(@NonNull Configuration newConfig) {
2170 // Run the default commands.
2171 super.onConfigurationChanged(newConfig);
2173 // Reload the ad for the free flavor if not in full screen mode.
2174 if (BuildConfig.FLAVOR.contentEquals("free") && !inFullScreenBrowsingMode) {
2175 // Get a handle for the ad view. This cannot be a class variable because it changes with each ad load.
2176 View adView = findViewById(R.id.adview);
2178 // Reload the ad. The AdView is destroyed and recreated, which changes the ID, every time it is reloaded to handle possible rotations.
2179 // `getContext()` can be used instead of `getActivity.getApplicationContext()` once the minimum API >= 23.
2180 AdHelper.loadAd(adView, getApplicationContext(), this, getString(R.string.ad_unit_id));
2183 // `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:
2184 // https://code.google.com/p/android/issues/detail?id=20493#c8
2185 // ActivityCompat.invalidateOptionsMenu(this);
2189 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
2190 // Get the hit test result.
2191 final WebView.HitTestResult hitTestResult = currentWebView.getHitTestResult();
2193 // Define the URL strings.
2194 final String imageUrl;
2195 final String linkUrl;
2197 // Get handles for the system managers.
2198 final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
2200 // Remove the lint errors below that the clipboard manager might be null.
2201 assert clipboardManager != null;
2203 // Process the link according to the type.
2204 switch (hitTestResult.getType()) {
2205 // `SRC_ANCHOR_TYPE` is a link.
2206 case WebView.HitTestResult.SRC_ANCHOR_TYPE:
2207 // Get the target URL.
2208 linkUrl = hitTestResult.getExtra();
2210 // Set the target URL as the title of the `ContextMenu`.
2211 menu.setHeaderTitle(linkUrl);
2213 // Add an Open in New Tab entry.
2214 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2215 // Load the link URL in a new tab and move to it.
2216 addNewTab(linkUrl, true);
2218 // Consume the event.
2222 // Add an Open in Background entry.
2223 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2224 // Load the link URL in a new tab but do not move to it.
2225 addNewTab(linkUrl, false);
2227 // Consume the event.
2231 // Add an Open with App entry.
2232 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2233 openWithApp(linkUrl);
2235 // Consume the event.
2239 // Add an Open with Browser entry.
2240 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2241 openWithBrowser(linkUrl);
2243 // Consume the event.
2247 // Add a Copy URL entry.
2248 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2249 // Save the link URL in a `ClipData`.
2250 ClipData srcAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2252 // Set the `ClipData` as the clipboard's primary clip.
2253 clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
2255 // Consume the event.
2259 // Add a Save URL entry.
2260 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2261 // Check the download preference.
2262 if (downloadWithExternalApp) { // Download with an external app.
2263 downloadUrlWithExternalApp(linkUrl);
2264 } else { // Handle the download inside of Privacy Browser.
2265 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2266 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2267 currentWebView.getAcceptCookies()).execute(linkUrl);
2270 // Consume the event.
2274 // Add an empty Cancel entry, which by default closes the context menu.
2275 menu.add(R.string.cancel);
2278 // `IMAGE_TYPE` is an image.
2279 case WebView.HitTestResult.IMAGE_TYPE:
2280 // Get the image URL.
2281 imageUrl = hitTestResult.getExtra();
2283 // Remove the incorrect lint warning below that the image URL might be null.
2284 assert imageUrl != null;
2286 // Set the context menu title.
2287 if (imageUrl.startsWith("data:")) { // The image data is contained in within the URL, making it exceedingly long.
2288 // Truncate the image URL before making it the title.
2289 menu.setHeaderTitle(imageUrl.substring(0, 100));
2290 } else { // The image URL does not contain the full image data.
2291 // Set the image URL as the title of the context menu.
2292 menu.setHeaderTitle(imageUrl);
2295 // Add an Open in New Tab entry.
2296 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2297 // Load the image in a new tab.
2298 addNewTab(imageUrl, true);
2300 // Consume the event.
2304 // Add an Open with App entry.
2305 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2306 // Open the image URL with an external app.
2307 openWithApp(imageUrl);
2309 // Consume the event.
2313 // Add an Open with Browser entry.
2314 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2315 // Open the image URL with an external browser.
2316 openWithBrowser(imageUrl);
2318 // Consume the event.
2322 // Add a View Image entry.
2323 menu.add(R.string.view_image).setOnMenuItemClickListener(item -> {
2324 // Load the image in the current tab.
2325 loadUrl(currentWebView, imageUrl);
2327 // Consume the event.
2331 // Add a Save Image entry.
2332 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2333 // Check the download preference.
2334 if (downloadWithExternalApp) { // Download with an external app.
2335 downloadUrlWithExternalApp(imageUrl);
2336 } else { // Handle the download inside of Privacy Browser.
2337 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2338 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2339 currentWebView.getAcceptCookies()).execute(imageUrl);
2342 // Consume the event.
2346 // Add a Copy URL entry.
2347 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2348 // Save the image URL in a clip data.
2349 ClipData imageTypeClipData = ClipData.newPlainText(getString(R.string.url), imageUrl);
2351 // Set the clip data as the clipboard's primary clip.
2352 clipboardManager.setPrimaryClip(imageTypeClipData);
2354 // Consume the event.
2358 // Add an empty Cancel entry, which by default closes the context menu.
2359 menu.add(R.string.cancel);
2362 // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
2363 case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
2364 // Get the image URL.
2365 imageUrl = hitTestResult.getExtra();
2367 // Instantiate a handler.
2368 Handler handler = new Handler();
2370 // Get a message from the handler.
2371 Message message = handler.obtainMessage();
2373 // Request the image details from the last touched node be returned in the message.
2374 currentWebView.requestFocusNodeHref(message);
2376 // Get the link URL from the message data.
2377 linkUrl = message.getData().getString("url");
2379 // Set the link URL as the title of the context menu.
2380 menu.setHeaderTitle(linkUrl);
2382 // Add an Open in New Tab entry.
2383 menu.add(R.string.open_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2384 // Load the link URL in a new tab and move to it.
2385 addNewTab(linkUrl, true);
2387 // Consume the event.
2391 // Add an Open in Background entry.
2392 menu.add(R.string.open_in_background).setOnMenuItemClickListener((MenuItem item) -> {
2393 // Lod the link URL in a new tab but do not move to it.
2394 addNewTab(linkUrl, false);
2396 // Consume the event.
2400 // Add an Open Image in New Tab entry.
2401 menu.add(R.string.open_image_in_new_tab).setOnMenuItemClickListener((MenuItem item) -> {
2402 // Load the image in a new tab and move to it.
2403 addNewTab(imageUrl, true);
2405 // Consume the event.
2409 // Add an Open with App entry.
2410 menu.add(R.string.open_with_app).setOnMenuItemClickListener((MenuItem item) -> {
2411 // Open the link URL with an external app.
2412 openWithApp(linkUrl);
2414 // Consume the event.
2418 // Add an Open with Browser entry.
2419 menu.add(R.string.open_with_browser).setOnMenuItemClickListener((MenuItem item) -> {
2420 // Open the link URL with an external browser.
2421 openWithBrowser(linkUrl);
2423 // Consume the event.
2427 // Add a View Image entry.
2428 menu.add(R.string.view_image).setOnMenuItemClickListener((MenuItem item) -> {
2429 // View the image in the current tab.
2430 loadUrl(currentWebView, imageUrl);
2432 // Consume the event.
2436 // Add a Save Image entry.
2437 menu.add(R.string.save_image).setOnMenuItemClickListener((MenuItem item) -> {
2438 // Check the download preference.
2439 if (downloadWithExternalApp) { // Download with an external app.
2440 downloadUrlWithExternalApp(imageUrl);
2441 } else { // Handle the download inside of Privacy Browser.
2442 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2443 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2444 currentWebView.getAcceptCookies()).execute(imageUrl);
2447 // Consume the event.
2451 // Add a Copy URL entry.
2452 menu.add(R.string.copy_url).setOnMenuItemClickListener((MenuItem item) -> {
2453 // Save the link URL in a clip data.
2454 ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getString(R.string.url), linkUrl);
2456 // Set the clip data as the clipboard's primary clip.
2457 clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
2459 // Consume the event.
2463 // Add a Save URL entry.
2464 menu.add(R.string.save_url).setOnMenuItemClickListener((MenuItem item) -> {
2465 // Check the download preference.
2466 if (downloadWithExternalApp) { // Download with an external app.
2467 downloadUrlWithExternalApp(linkUrl);
2468 } else { // Handle the download inside of Privacy Browser.
2469 // Prepare the save dialog. The dialog will be displayed once the file size and the content disposition have been acquired.
2470 new PrepareSaveDialog(this, this, getSupportFragmentManager(), SaveWebpageDialog.SAVE_URL, currentWebView.getSettings().getUserAgentString(),
2471 currentWebView.getAcceptCookies()).execute(linkUrl);
2474 // Consume the event.
2478 // Add an empty Cancel entry, which by default closes the context menu.
2479 menu.add(R.string.cancel);
2482 case WebView.HitTestResult.EMAIL_TYPE:
2483 // Get the target URL.
2484 linkUrl = hitTestResult.getExtra();
2486 // Set the target URL as the title of the `ContextMenu`.
2487 menu.setHeaderTitle(linkUrl);
2489 // Add a Write Email entry.
2490 menu.add(R.string.write_email).setOnMenuItemClickListener(item -> {
2491 // Use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
2492 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
2494 // Parse the url and set it as the data for the `Intent`.
2495 emailIntent.setData(Uri.parse("mailto:" + linkUrl));
2497 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
2498 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
2502 startActivity(emailIntent);
2503 } catch (ActivityNotFoundException exception) {
2504 // Display a snackbar.
2505 Snackbar.make(currentWebView, getString(R.string.error) + " " + exception, Snackbar.LENGTH_INDEFINITE).show();
2508 // Consume the event.
2512 // Add a Copy Email Address entry.
2513 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(item -> {
2514 // Save the email address in a `ClipData`.
2515 ClipData srcEmailTypeClipData = ClipData.newPlainText(getString(R.string.email_address), linkUrl);
2517 // Set the `ClipData` as the clipboard's primary clip.
2518 clipboardManager.setPrimaryClip(srcEmailTypeClipData);
2520 // Consume the event.
2524 // Add an empty Cancel entry, which by default closes the context menu.
2525 menu.add(R.string.cancel);
2531 public void onCreateBookmark(DialogFragment dialogFragment, Bitmap favoriteIconBitmap) {
2532 // Get a handle for the bookmarks list view.
2533 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2536 Dialog dialog = dialogFragment.getDialog();
2538 // Remove the incorrect lint warning below that the dialog might be null.
2539 assert dialog != null;
2541 // Get the views from the dialog fragment.
2542 EditText createBookmarkNameEditText = dialog.findViewById(R.id.create_bookmark_name_edittext);
2543 EditText createBookmarkUrlEditText = dialog.findViewById(R.id.create_bookmark_url_edittext);
2545 // Extract the strings from the edit texts.
2546 String bookmarkNameString = createBookmarkNameEditText.getText().toString();
2547 String bookmarkUrlString = createBookmarkUrlEditText.getText().toString();
2549 // Create a favorite icon byte array output stream.
2550 ByteArrayOutputStream favoriteIconByteArrayOutputStream = new ByteArrayOutputStream();
2552 // Convert the favorite icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2553 favoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, favoriteIconByteArrayOutputStream);
2555 // Convert the favorite icon byte array stream to a byte array.
2556 byte[] favoriteIconByteArray = favoriteIconByteArrayOutputStream.toByteArray();
2558 // Display the new bookmark below the current items in the (0 indexed) list.
2559 int newBookmarkDisplayOrder = bookmarksListView.getCount();
2561 // Create the bookmark.
2562 bookmarksDatabaseHelper.createBookmark(bookmarkNameString, bookmarkUrlString, currentBookmarksFolder, newBookmarkDisplayOrder, favoriteIconByteArray);
2564 // Update the bookmarks cursor with the current contents of this folder.
2565 bookmarksCursor = bookmarksDatabaseHelper.getBookmarksByDisplayOrder(currentBookmarksFolder);
2567 // Update the list view.
2568 bookmarksCursorAdapter.changeCursor(bookmarksCursor);
2570 // Scroll to the new bookmark.
2571 bookmarksListView.setSelection(newBookmarkDisplayOrder);
2575 public void onCreateBookmarkFolder(DialogFragment dialogFragment, @NonNull Bitmap favoriteIconBitmap) {
2576 // Get a handle for the bookmarks list view.
2577 ListView bookmarksListView = findViewById(R.id.bookmarks_drawer_listview);
2580 Dialog dialog = dialogFragment.getDialog();
2582 // Remove the incorrect lint warning below that the dialog might be null.
2583 assert dialog != null;
2585 // Get handles for the views in the dialog fragment.
2586 EditText folderNameEditText = dialog.findViewById(R.id.folder_name_edittext);
2587 RadioButton defaultIconRadioButton = dialog.findViewById(R.id.default_icon_radiobutton);
2588 ImageView defaultIconImageView = dialog.findViewById(R.id.default_icon_imageview);
2590 // Get new folder name string.
2591 String folderNameString = folderNameEditText.getText().toString();
2593 // Create a folder icon bitmap.
2594 Bitmap folderIconBitmap;
2596 // Set the folder icon bitmap according to the dialog.
2597 if (defaultIconRadioButton.isChecked()) { // Use the default folder icon.
2598 // Get the default folder icon drawable.
2599 Drawable folderIconDrawable = defaultIconImageView.getDrawable();
2601 // Convert the folder icon drawable to a bitmap drawable.
2602 BitmapDrawable folderIconBitmapDrawable = (BitmapDrawable) folderIconDrawable;
2604 // Convert the folder icon bitmap drawable to a bitmap.
2605 folderIconBitmap = folderIconBitmapDrawable.getBitmap();
2606 } else { // Use the WebView favorite icon.
2607 // Copy the favorite icon bitmap to the folder icon bitmap.
2608 folderIconBitmap = favoriteIconBitmap;
2611 // Create a folder icon byte array output stream.
2612 ByteArrayOutputStream folderIconByteArrayOutputStream = new ByteArrayOutputStream();
2614 // Convert the folder icon bitmap to a byte array. `0` is for lossless compression (the only option for a PNG).
2615 folderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, folderIconByteArrayOutputStream);
2617 // Convert the folder icon byte array stream to a byte array.
2618 byte[] folderIconByteArray = folderIconByteArrayOutputStream.toByteArray();
2620 // Move all the bookmarks down one in the display order.
2621 for (int i = 0; i < bookmarksListView.getCount(); i++) {
2622 int databaseId = (int) bookmarksListView.getItemIdAtPosition(i);
2623 bookmarksDatabaseHelper.updateDisplayOrder(databaseId, i + 1);
2626 // Create the folder, which will be placed at the top of the `ListView`.
2627 bookmarksDatabaseHelper.createFolder(folderNameString, currentBookmarksFolder, folderIconByteArray);