2 * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser;
22 import android.annotation.SuppressLint;
23 import android.app.DialogFragment;
24 import android.app.DownloadManager;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.SharedPreferences;
28 import android.content.res.Configuration;
29 import android.graphics.Bitmap;
30 import android.graphics.drawable.BitmapDrawable;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.net.http.SslCertificate;
34 import android.net.http.SslError;
35 import android.os.Build;
36 import android.os.Bundle;
37 import android.preference.PreferenceManager;
38 import android.print.PrintDocumentAdapter;
39 import android.print.PrintManager;
40 import android.support.annotation.NonNull;
41 import android.support.design.widget.NavigationView;
42 import android.support.design.widget.Snackbar;
43 import android.support.v4.app.ActivityCompat;
44 import android.support.v4.content.ContextCompat;
45 import android.support.v4.view.GravityCompat;
46 import android.support.v4.widget.DrawerLayout;
47 import android.support.v4.widget.SwipeRefreshLayout;
48 import android.support.v7.app.ActionBar;
49 import android.support.v7.app.ActionBarDrawerToggle;
50 import android.support.v7.app.AppCompatActivity;
51 import android.support.v7.app.AppCompatDialogFragment;
52 import android.support.v7.widget.Toolbar;
53 import android.util.Patterns;
54 import android.view.KeyEvent;
55 import android.view.Menu;
56 import android.view.MenuItem;
57 import android.view.View;
58 import android.view.inputmethod.InputMethodManager;
59 import android.webkit.CookieManager;
60 import android.webkit.DownloadListener;
61 import android.webkit.SslErrorHandler;
62 import android.webkit.WebChromeClient;
63 import android.webkit.WebStorage;
64 import android.webkit.WebView;
65 import android.webkit.WebViewClient;
66 import android.webkit.WebViewDatabase;
67 import android.widget.EditText;
68 import android.widget.FrameLayout;
69 import android.widget.ImageView;
70 import android.widget.LinearLayout;
71 import android.widget.ProgressBar;
73 import java.io.UnsupportedEncodingException;
74 import java.net.MalformedURLException;
76 import java.net.URLEncoder;
77 import java.util.HashMap;
80 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
81 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener,
82 SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener {
84 // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.
85 // It is also used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
86 public static ActionBar appBar;
88 // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
89 // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
90 public static Bitmap favoriteIcon;
92 // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`.
93 // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
94 public static String formattedUrlString;
96 // `sslCertificate` is public static so it can be accessed from `ViewSslCertificate`. It is also used in `onCreate()`.
97 public static SslCertificate sslCertificate;
100 // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `loadUrlFromTextBox()`.
101 private WebView mainWebView;
103 // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
104 private SwipeRefreshLayout swipeRefreshLayout;
106 // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, and `onRestart()`.
107 private CookieManager cookieManager;
109 // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
110 private final Map<String, String> customHeaders = new HashMap<>();
112 // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`.
113 // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`.
114 private Boolean javaScriptEnabled;
116 // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
117 private boolean firstPartyCookiesEnabled;
119 // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
120 private boolean thirdPartyCookiesEnabled;
122 // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
123 private boolean domStorageEnabled;
125 // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
126 private boolean saveFormDataEnabled;
128 // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`.
129 private boolean swipeToRefreshEnabled;
131 // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`.
132 private String homepage;
134 // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
135 private String javaScriptDisabledSearchURL;
137 // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
138 private String javaScriptEnabledSearchURL;
140 // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
141 private Menu mainMenu;
143 // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
144 private ActionBarDrawerToggle drawerToggle;
146 // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
147 private DrawerLayout drawerLayout;
149 // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
150 private EditText urlTextBox;
152 // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
155 // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
156 private SslErrorHandler sslErrorHandler;
158 // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
159 private EditText findOnPageEditText;
161 // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
162 private InputMethodManager inputMethodManager;
165 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled. The whole premise of Privacy Browser is built around an understanding of these dangers.
166 @SuppressLint("SetJavaScriptEnabled")
167 protected void onCreate(Bundle savedInstanceState) {
168 super.onCreate(savedInstanceState);
169 setContentView(R.layout.main_coordinatorlayout);
171 // Get a handle for `inputMethodManager`.
172 inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
174 // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
175 Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
176 setSupportActionBar(supportAppBar);
177 appBar = getSupportActionBar();
179 // This is needed to get rid of the Android Studio warning that appBar might be null.
180 assert appBar != null;
182 // Add the custom url_app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
183 appBar.setCustomView(R.layout.url_app_bar);
184 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
186 // Set the "go" button on the keyboard to load the URL in urlTextBox.
187 urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
188 urlTextBox.setOnKeyListener(new View.OnKeyListener() {
190 public boolean onKey(View v, int keyCode, KeyEvent event) {
191 // If the event is a key-down event on the `enter` button, load the URL.
192 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
193 // Load the URL into the mainWebView and consume the event.
195 loadUrlFromTextBox();
196 } catch (UnsupportedEncodingException e) {
199 // If the enter key was pressed, consume the event.
202 // If any other key was pressed, do not consume the event.
208 // Get a handle for `find_on_page_edittext`.
209 findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
211 // Set the `go` button on the keyboard to search for the phrase in `find_on_page_edittext`
212 findOnPageEditText.setOnKeyListener(new View.OnKeyListener() {
214 public boolean onKey(View v, int keyCode, KeyEvent event) {
215 // If the event is a key-down event on the `enter` button, search for the phrase.
216 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
217 // Search for the phrase.
218 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
220 // Consume the event.
223 // Do not consume the event.
229 final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
231 // Implement swipe to refresh
232 swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
233 swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
234 swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
236 public void onRefresh() {
237 mainWebView.reload();
241 mainWebView = (WebView) findViewById(R.id.mainWebView);
243 // Create the navigation drawer.
244 drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
245 // The DrawerTitle identifies the drawer in accessibility mode.
246 drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
248 // Listen for touches on the navigation menu.
249 final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
250 navigationView.setNavigationItemSelectedListener(this);
252 // drawerToggle creates the hamburger icon at the start of the AppBar.
253 drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
255 mainWebView.setWebViewClient(new WebViewClient() {
256 // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
257 // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
258 @SuppressWarnings("deprecation")
260 public boolean shouldOverrideUrlLoading(WebView view, String url) {
261 // Use an external email program if the link begins with "mailto:".
262 if (url.startsWith("mailto:")) {
263 // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
264 Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
266 // Parse the url and set it as the data for the `Intent`.
267 emailIntent.setData(Uri.parse(url));
269 // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
270 emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
273 startActivity(emailIntent);
275 } else { // Load the URL in Privacy Browser.
276 mainWebView.loadUrl(url, customHeaders);
281 // Update the URL in urlTextBox when the page starts to load.
283 public void onPageStarted(WebView view, String url, Bitmap favicon) {
284 // We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
285 formattedUrlString = url;
287 // Display the loading URL is the URL text box.
288 urlTextBox.setText(url);
291 // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load.
293 public void onPageFinished(WebView view, String url) {
294 formattedUrlString = url;
296 // Only update urlTextBox if the user is not typing in it.
297 if (!urlTextBox.hasFocus()) {
298 urlTextBox.setText(formattedUrlString);
301 // Store the SSL certificate so it can be accessed from `ViewSslCertificate`.
302 sslCertificate = mainWebView.getCertificate();
305 // Handle SSL Certificate errors.
307 public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
308 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
309 sslErrorHandler = handler;
311 // Display the SSL error `AlertDialog`.
312 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
313 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
317 mainWebView.setWebChromeClient(new WebChromeClient() {
318 // Update the progress bar when a page is loading.
320 public void onProgressChanged(WebView view, int progress) {
321 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
322 progressBar.setProgress(progress);
323 if (progress < 100) {
324 progressBar.setVisibility(View.VISIBLE);
326 progressBar.setVisibility(View.GONE);
328 //Stop the `SwipeToRefresh` indicator if it is running
329 swipeRefreshLayout.setRefreshing(false);
333 // Set the favorite icon when it changes.
335 public void onReceivedIcon(WebView view, Bitmap icon) {
336 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
339 // Place the favorite icon in the appBar.
340 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
341 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
344 // Enter full screen video
346 public void onShowCustomView(View view, CustomViewCallback callback) {
349 // Show the fullScreenVideoFrameLayout.
350 fullScreenVideoFrameLayout.addView(view);
351 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
353 // Hide the mainWebView.
354 mainWebView.setVisibility(View.GONE);
356 // Hide the ad if this is the free flavor.
357 BannerAd.hideAd(adView);
359 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
360 * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
361 * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
363 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
366 // Exit full screen video
367 public void onHideCustomView() {
370 // Show the mainWebView.
371 mainWebView.setVisibility(View.VISIBLE);
373 // Show the ad if this is the free flavor.
374 BannerAd.showAd(adView);
376 // Hide the fullScreenVideoFrameLayout.
377 fullScreenVideoFrameLayout.removeAllViews();
378 fullScreenVideoFrameLayout.setVisibility(View.GONE);
382 // Allow the downloading of files.
383 mainWebView.setDownloadListener(new DownloadListener() {
385 public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
386 // Show the `DownloadFile` `AlertDialog` and name this instance `@string/download`.
387 AppCompatDialogFragment downloadFileDialogFragment = DownloadFile.fromUrl(url, contentDisposition, contentLength);
388 downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
392 // Allow pinch to zoom.
393 mainWebView.getSettings().setBuiltInZoomControls(true);
395 // Hide zoom controls.
396 mainWebView.getSettings().setDisplayZoomControls(false);
398 // Initialize cookieManager.
399 cookieManager = CookieManager.getInstance();
401 // Replace the header that `WebView` creates for `X-Requested-With` with a null value. The default value is the application ID (com.stoutner.privacybrowser.standard).
402 customHeaders.put("X-Requested-With", "");
404 // Initialize the default preference values the first time the program is run.
405 PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
407 // Apply the settings from the shared preferences.
410 // Get the intent information that started the app.
411 final Intent intent = getIntent();
413 if (intent.getData() != null) {
414 // Get the intent data and convert it to a string.
415 final Uri intentUriData = intent.getData();
416 formattedUrlString = intentUriData.toString();
419 // If formattedUrlString is null assign the homepage to it.
420 if (formattedUrlString == null) {
421 formattedUrlString = homepage;
424 // Load the initial website.
425 mainWebView.loadUrl(formattedUrlString, customHeaders);
427 // If the favorite icon is null, load the default.
428 if (favoriteIcon == null) {
429 // We have to use `ContextCompat` until API >= 21.
430 Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
431 BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
432 favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
435 // Initialize AdView for the free flavor and request an ad. If this is not the free flavor BannerAd.requestAd() does nothing.
436 adView = findViewById(R.id.adView);
437 BannerAd.requestAd(adView);
442 protected void onNewIntent(Intent intent) {
443 // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
446 if (intent.getData() != null) {
447 // Get the intent data and convert it to a string.
448 final Uri intentUriData = intent.getData();
449 formattedUrlString = intentUriData.toString();
452 // Close the navigation drawer if it is open.
453 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
454 drawerLayout.closeDrawer(GravityCompat.START);
458 mainWebView.loadUrl(formattedUrlString, customHeaders);
460 // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
461 mainWebView.requestFocus();
465 public boolean onCreateOptionsMenu(Menu menu) {
466 // Inflate the menu; this adds items to the action bar if it is present.
467 getMenuInflater().inflate(R.menu.webview_options_menu, menu);
469 // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
472 // Set the initial status of the privacy icons. `false` does not call `invalidateOptionsMenu` as the last step.
473 updatePrivacyIcons(false);
475 // Get handles for the menu items.
476 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
477 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
478 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
479 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
481 // Only display third-Party Cookies if SDK >= 21
482 toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21);
484 // Get the shared preference values. `this` references the current context.
485 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
487 // Set the status of the additional app bar icons. The default is `false`.
488 if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
489 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
490 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
491 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
492 } else { //Do not display the additional icons.
493 toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
494 toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
495 toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
502 public boolean onPrepareOptionsMenu(Menu menu) {
503 // Get handles for the menu items.
504 MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
505 MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
506 MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
507 MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
508 MenuItem clearCookies = menu.findItem(R.id.clearCookies);
509 MenuItem clearFormData = menu.findItem(R.id.clearFormData);
510 MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
512 // Set the status of the menu item checkboxes.
513 toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
514 toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
515 toggleDomStorage.setChecked(domStorageEnabled);
516 toggleSaveFormData.setChecked(saveFormDataEnabled);
518 // Enable third-party cookies if first-party cookies are enabled.
519 toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled);
521 // Enable DOM Storage if JavaScript is enabled.
522 toggleDomStorage.setEnabled(javaScriptEnabled);
524 // Enable Clear Cookies if there are any.
525 clearCookies.setEnabled(cookieManager.hasCookies());
527 // Enable Clear Form Data is there is any.
528 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
529 clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
531 // Only show `Refresh` if `swipeToRefresh` is disabled.
532 refreshMenuItem.setVisible(!swipeToRefreshEnabled);
534 // Initialize font size variables.
535 int fontSize = mainWebView.getSettings().getTextZoom();
536 String fontSizeTitle;
537 MenuItem selectedFontSizeMenuItem;
539 // Prepare the font size title and current size menu item.
542 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
543 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
547 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
548 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
552 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
553 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
557 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
558 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
562 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
563 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
567 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
568 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
572 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
573 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
577 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
578 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
582 // Set the font size title and select the current size menu item.
583 MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
584 fontSizeMenuItem.setTitle(fontSizeTitle);
585 selectedFontSizeMenuItem.setChecked(true);
587 // Run all the other default commands.
588 super.onPrepareOptionsMenu(menu);
590 // `return true` displays the menu.
595 // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
596 @SuppressLint("SetJavaScriptEnabled")
597 // removeAllCookies is deprecated, but it is required for API < 21.
598 @SuppressWarnings("deprecation")
599 public boolean onOptionsItemSelected(MenuItem menuItem) {
600 int menuItemId = menuItem.getItemId();
602 // Set the commands that relate to the menu entries.
603 switch (menuItemId) {
604 case R.id.toggleJavaScript:
605 // Switch the status of javaScriptEnabled.
606 javaScriptEnabled = !javaScriptEnabled;
608 // Apply the new JavaScript status.
609 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
611 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
612 updatePrivacyIcons(true);
614 // Display a `Snackbar`.
615 if (javaScriptEnabled) { // JavaScrip is enabled.
616 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
617 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled, but first-party cookies are enabled.
618 Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
619 } else { // Privacy mode.
620 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
623 // Reload the WebView.
624 mainWebView.reload();
627 case R.id.toggleFirstPartyCookies:
628 // Switch the status of firstPartyCookiesEnabled.
629 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
631 // Update the menu checkbox.
632 menuItem.setChecked(firstPartyCookiesEnabled);
634 // Apply the new cookie status.
635 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
637 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
638 updatePrivacyIcons(true);
640 // Display a `Snackbar`.
641 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
642 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
643 } else if (javaScriptEnabled){ // JavaScript is still enabled.
644 Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
645 } else { // Privacy mode.
646 Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
649 // Reload the WebView.
650 mainWebView.reload();
653 case R.id.toggleThirdPartyCookies:
654 if (Build.VERSION.SDK_INT >= 21) {
655 // Switch the status of thirdPartyCookiesEnabled.
656 thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
658 // Update the menu checkbox.
659 menuItem.setChecked(thirdPartyCookiesEnabled);
661 // Apply the new cookie status.
662 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
664 // Display a `Snackbar`.
665 if (thirdPartyCookiesEnabled) {
666 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
668 Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
671 // Reload the WebView.
672 mainWebView.reload();
673 } // Else do nothing because SDK < 21.
676 case R.id.toggleDomStorage:
677 // Switch the status of domStorageEnabled.
678 domStorageEnabled = !domStorageEnabled;
680 // Update the menu checkbox.
681 menuItem.setChecked(domStorageEnabled);
683 // Apply the new DOM Storage status.
684 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
686 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
687 updatePrivacyIcons(true);
689 // Display a `Snackbar`.
690 if (domStorageEnabled) {
691 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
693 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
696 // Reload the WebView.
697 mainWebView.reload();
700 case R.id.toggleSaveFormData:
701 // Switch the status of saveFormDataEnabled.
702 saveFormDataEnabled = !saveFormDataEnabled;
704 // Update the menu checkbox.
705 menuItem.setChecked(saveFormDataEnabled);
707 // Apply the new form data status.
708 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
710 // Display a `Snackbar`.
711 if (saveFormDataEnabled) {
712 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
714 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
717 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
718 updatePrivacyIcons(true);
720 // Reload the WebView.
721 mainWebView.reload();
724 case R.id.clearCookies:
725 if (Build.VERSION.SDK_INT < 21) {
726 cookieManager.removeAllCookie();
728 cookieManager.removeAllCookies(null);
730 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
733 case R.id.clearDomStorage:
734 WebStorage webStorage = WebStorage.getInstance();
735 webStorage.deleteAllData();
736 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
739 case R.id.clearFormData:
740 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
741 mainWebViewDatabase.clearFormData();
742 mainWebView.reload();
745 case R.id.fontSizeFiftyPercent:
746 mainWebView.getSettings().setTextZoom(50);
749 case R.id.fontSizeSeventyFivePercent:
750 mainWebView.getSettings().setTextZoom(75);
753 case R.id.fontSizeOneHundredPercent:
754 mainWebView.getSettings().setTextZoom(100);
757 case R.id.fontSizeOneHundredTwentyFivePercent:
758 mainWebView.getSettings().setTextZoom(125);
761 case R.id.fontSizeOneHundredFiftyPercent:
762 mainWebView.getSettings().setTextZoom(150);
765 case R.id.fontSizeOneHundredSeventyFivePercent:
766 mainWebView.getSettings().setTextZoom(175);
769 case R.id.fontSizeTwoHundredPercent:
770 mainWebView.getSettings().setTextZoom(200);
773 case R.id.find_on_page:
774 // Hide the URL app bar.
775 Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar);
776 appBarToolbar.setVisibility(View.GONE);
778 // Show the Find on Page `RelativeLayout`.
779 LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
780 findOnPageLinearLayout.setVisibility(View.VISIBLE);
782 // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android.
783 findOnPageEditText.postDelayed(new Runnable()
788 // Set the focus on `findOnPageEditText`.
789 findOnPageEditText.requestFocus();
791 // Display the keyboard.
792 inputMethodManager.showSoftInput(findOnPageEditText, 0);
798 Intent shareIntent = new Intent();
799 shareIntent.setAction(Intent.ACTION_SEND);
800 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
801 shareIntent.setType("text/plain");
802 startActivity(Intent.createChooser(shareIntent, "Share URL"));
805 case R.id.addToHomescreen:
806 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `@string/create_shortcut`.
807 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
808 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut));
810 //Everything else will be handled by `CreateHomeScreenShortcut` and the associated listener below.
814 // Get a `PrintManager` instance.
815 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
817 // Convert `mainWebView` to `printDocumentAdapter`.
818 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
820 // Print the document. The print attributes are `null`.
821 printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
825 mainWebView.reload();
829 // Don't consume the event.
830 return super.onOptionsItemSelected(menuItem);
834 // removeAllCookies is deprecated, but it is required for API < 21.
835 @SuppressWarnings("deprecation")
837 public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
838 int menuItemId = menuItem.getItemId();
840 switch (menuItemId) {
842 mainWebView.loadUrl(homepage, customHeaders);
846 if (mainWebView.canGoBack()) {
847 mainWebView.goBack();
852 if (mainWebView.canGoForward()) {
853 mainWebView.goForward();
858 // Launch BookmarksActivity.
859 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
860 startActivity(bookmarksIntent);
864 // Launch the system Download Manager.
865 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
867 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
868 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
870 startActivity(downloadManagerIntent);
874 // Launch `SettingsActivity`.
875 Intent settingsIntent = new Intent(this, SettingsActivity.class);
876 startActivity(settingsIntent);
880 // Launch `GuideActivity`.
881 Intent guideIntent = new Intent(this, GuideActivity.class);
882 startActivity(guideIntent);
886 // Launch `AboutActivity`.
887 Intent aboutIntent = new Intent(this, AboutActivity.class);
888 startActivity(aboutIntent);
891 case R.id.clearAndExit:
892 // Clear cookies. The commands changed slightly in API 21.
893 if (Build.VERSION.SDK_INT >= 21) {
894 cookieManager.removeAllCookies(null);
896 cookieManager.removeAllCookie();
899 // Clear DOM storage.
900 WebStorage domStorage = WebStorage.getInstance();
901 domStorage.deleteAllData();
904 WebViewDatabase formData = WebViewDatabase.getInstance(this);
905 formData.clearFormData();
907 // Clear cache. The argument of "true" includes disk files.
908 mainWebView.clearCache(true);
910 // Clear the back/forward history.
911 mainWebView.clearHistory();
913 // Clear any SSL certificate preferences.
914 mainWebView.clearSslPreferences();
916 // Clear `formattedUrlString`.
917 formattedUrlString = null;
919 // Clear `customHeaders`.
920 customHeaders.clear();
922 // Destroy the internal state of the webview.
923 mainWebView.destroy();
925 // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
926 if (Build.VERSION.SDK_INT >= 21) {
927 finishAndRemoveTask();
932 // Remove the terminated program from RAM. The status code is `0`.
940 // Close the navigation drawer.
941 drawerLayout.closeDrawer(GravityCompat.START);
946 public void onPostCreate(Bundle savedInstanceState) {
947 super.onPostCreate(savedInstanceState);
949 // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
950 drawerToggle.syncState();
954 public void onConfigurationChanged(Configuration newConfig) {
955 super.onConfigurationChanged(newConfig);
957 // Reload the ad if this is the free flavor.
958 BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
960 // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
961 adView = findViewById(R.id.adView);
963 // `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: https://code.google.com/p/android/issues/detail?id=20493#c8
964 // ActivityCompat.invalidateOptionsMenu(this);
968 public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
969 // Get shortcutNameEditText from the alert dialog.
970 EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
972 // Create the bookmark shortcut based on formattedUrlString.
973 Intent bookmarkShortcut = new Intent();
974 bookmarkShortcut.setAction(Intent.ACTION_VIEW);
975 bookmarkShortcut.setData(Uri.parse(formattedUrlString));
977 // Place the bookmark shortcut on the home screen.
978 Intent placeBookmarkShortcut = new Intent();
979 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
980 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
981 placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
982 placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
983 sendBroadcast(placeBookmarkShortcut);
987 public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
988 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
989 DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
991 // Get the file name from `dialogFragment`.
992 EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
993 String fileName = downloadFileNameEditText.getText().toString();
995 // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
996 if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `fileName`.
997 downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
998 } else { // Only set the title using `fileName`.
999 downloadRequest.setTitle(fileName);
1002 // Allow `MediaScanner` to index the download if it is a media file.
1003 downloadRequest.allowScanningByMediaScanner();
1005 // Add the URL as the description for the download.
1006 downloadRequest.setDescription(downloadUrl);
1008 // Show the download notification after the download is completed.
1009 downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1011 // Initiate the download and display a Snackbar.
1012 downloadManager.enqueue(downloadRequest);
1015 public void viewSslCertificate(View view) {
1016 // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
1017 DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
1018 viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
1022 public void onSslErrorCancel() {
1023 sslErrorHandler.cancel();
1027 public void onSslErrorProceed() {
1028 sslErrorHandler.proceed();
1031 // Override onBackPressed to handle the navigation drawer and mainWebView.
1033 public void onBackPressed() {
1034 final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
1036 // Close the navigation drawer if it is available. GravityCompat.START is the drawer on the left on Left-to-Right layout text.
1037 if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1038 drawerLayout.closeDrawer(GravityCompat.START);
1040 // Load the previous URL if available.
1041 if (mainWebView.canGoBack()) {
1042 mainWebView.goBack();
1044 // Pass onBackPressed to the system.
1045 super.onBackPressed();
1051 public void onPause() {
1052 // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
1053 BannerAd.pauseAd(adView);
1059 public void onResume() {
1062 // We need to resume the adView for the free flavor.
1063 BannerAd.resumeAd(adView);
1067 public void onRestart() {
1070 // Apply the settings from shared preferences, which might have been changed in `SettingsActivity`.
1073 // Update the privacy icon. `true` runs `invalidateOptionsMenu` as the last step.
1074 updatePrivacyIcons(true);
1078 private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1079 // Get the text from urlTextBox and convert it to a string. trim() removes white spaces from the beginning and end of the string.
1080 String unformattedUrlString = urlTextBox.getText().toString().trim();
1082 URL unformattedUrl = null;
1083 Uri.Builder formattedUri = new Uri.Builder();
1085 // Check to see if unformattedUrlString is a valid URL. Otherwise, convert it into a Duck Duck Go search.
1086 if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1087 // Add http:// at the beginning if it is missing. Otherwise the app will segfault.
1088 if (!unformattedUrlString.startsWith("http")) {
1089 unformattedUrlString = "http://" + unformattedUrlString;
1092 // Convert unformattedUrlString to a URL, then to a URI, and then back to a string, which sanitizes the input and adds in any missing components.
1094 unformattedUrl = new URL(unformattedUrlString);
1095 } catch (MalformedURLException e) {
1096 e.printStackTrace();
1099 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1100 final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1101 final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1102 final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1103 final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1104 final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1106 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1107 formattedUrlString = formattedUri.build().toString();
1109 // Sanitize the search input and convert it to a DuckDuckGo search.
1110 final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1112 // Use the correct search URL.
1113 if (javaScriptEnabled) { // JavaScript is enabled.
1114 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1115 } else { // JavaScript is disabled.
1116 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1120 mainWebView.loadUrl(formattedUrlString, customHeaders);
1122 // Hides the keyboard so we can see the webpage. `0` indicates no additional flags.
1123 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1126 public void closeFindOnPage(View view) {
1127 // Delete the contents of `find_on_page_edittext`.
1128 findOnPageEditText.setText(null);
1130 // Hide the Find on Page `RelativeLayout`.
1131 LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
1132 findOnPageLinearLayout.setVisibility(View.GONE);
1134 // Show the URL app bar.
1135 Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar);
1136 appBarToolbar.setVisibility(View.VISIBLE);
1138 // Hides the keyboard so we can see the webpage. `0` indicates no additional flags.
1139 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1142 private void applySettings() {
1143 // Get the shared preference values. `this` references the current context.
1144 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1146 // Store the values from `sharedPreferences` in variables.
1147 String userAgentString = sharedPreferences.getString("user_agent", "Default user agent");
1148 String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
1149 String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
1150 String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
1151 String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
1152 String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
1153 String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
1154 String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
1155 swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
1156 boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true);
1157 boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
1159 // Because they can be modified on-the-fly by the user, these default settings are only applied when the program first runs.
1160 if (javaScriptEnabled == null) { // If `javaScriptEnabled` is null the program is just starting.
1161 // Get the values from `sharedPreferences`.
1162 javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
1163 firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
1164 thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
1165 domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
1166 saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
1168 // Apply the default settings.
1169 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
1170 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1171 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1172 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1173 mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
1175 // Set third-party cookies status if API >= 21.
1176 if (Build.VERSION.SDK_INT >= 21) {
1177 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1181 // Apply the other settings from `sharedPreferences`.
1182 homepage = homepageString;
1183 swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
1185 // Set the user agent initial status.
1186 switch (userAgentString) {
1187 case "Default user agent":
1188 // Set the user agent to `""`, which uses the default value.
1189 mainWebView.getSettings().setUserAgentString("");
1192 case "Custom user agent":
1193 // Set the custom user agent.
1194 mainWebView.getSettings().setUserAgentString(customUserAgentString);
1198 // Use the selected user agent.
1199 mainWebView.getSettings().setUserAgentString(userAgentString);
1203 // Set JavaScript disabled search.
1204 if (javaScriptDisabledSearchString.equals("Custom URL")) { // Get the custom URL string.
1205 javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString;
1206 } else { // Use the string from the pre-built list.
1207 javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
1210 // Set JavaScript enabled search.
1211 if (javaScriptEnabledSearchString.equals("Custom URL")) { // Get the custom URL string.
1212 javaScriptEnabledSearchURL = javaScriptEnabledCustomSearchString;
1213 } else { // Use the string from the pre-built list.
1214 javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
1217 // Set Do Not Track status.
1218 if (doNotTrackEnabled) {
1219 customHeaders.put("DNT", "1");
1221 customHeaders.remove("DNT");
1224 // Set Orbot proxy status.
1225 if (proxyThroughOrbot) {
1226 // Set the proxy. `this` refers to the current activity where an `AlertDialog` might be displayed.
1227 OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
1228 } else { // Reset the proxy to default. The host is `""` and the port is `"0"`.
1229 OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
1233 private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
1234 // Get handles for the icons.
1235 MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
1236 MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
1237 MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
1238 MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
1240 // Update `privacyIcon`.
1241 if (javaScriptEnabled) { // JavaScript is enabled.
1242 privacyIcon.setIcon(R.drawable.javascript_enabled);
1243 } else if (firstPartyCookiesEnabled) { // JavaScript is disabled but cookies are enabled.
1244 privacyIcon.setIcon(R.drawable.warning);
1245 } else { // All the dangerous features are disabled.
1246 privacyIcon.setIcon(R.drawable.privacy_mode);
1249 // Update `firstPartyCookiesIcon`.
1250 if (firstPartyCookiesEnabled) { // First-party cookies are enabled.
1251 firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled);
1252 } else { // First-party cookies are disabled.
1253 firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1256 // Update `domStorageIcon`.
1257 if (javaScriptEnabled && domStorageEnabled) { // Both JavaScript and DOM storage are enabled.
1258 domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
1259 } else if (javaScriptEnabled) { // JavaScript is enabled but DOM storage is disabled.
1260 domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
1261 } else { // JavaScript is disabled, so DOM storage is ghosted.
1262 domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
1265 // Update `formDataIcon`.
1266 if (saveFormDataEnabled) { // Form data is enabled.
1267 formDataIcon.setIcon(R.drawable.form_data_enabled);
1268 } else { // Form data is disabled.
1269 formDataIcon.setIcon(R.drawable.form_data_disabled);
1272 // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`. `this` references the current activity.
1273 if (runInvalidateOptionsMenu) {
1274 ActivityCompat.invalidateOptionsMenu(this);