Enable printing.
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / MainWebViewActivity.java
1 /**
2  * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 package com.stoutner.privacybrowser;
21
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.DialogFragment;
25 import android.app.DownloadManager;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.res.Configuration;
30 import android.graphics.Bitmap;
31 import android.graphics.drawable.BitmapDrawable;
32 import android.graphics.drawable.Drawable;
33 import android.net.Uri;
34 import android.net.http.SslCertificate;
35 import android.net.http.SslError;
36 import android.os.Build;
37 import android.os.Bundle;
38 import android.preference.PreferenceManager;
39 import android.print.PrintDocumentAdapter;
40 import android.print.PrintManager;
41 import android.support.annotation.NonNull;
42 import android.support.design.widget.NavigationView;
43 import android.support.design.widget.Snackbar;
44 import android.support.v4.app.ActivityCompat;
45 import android.support.v4.content.ContextCompat;
46 import android.support.v4.view.GravityCompat;
47 import android.support.v4.widget.DrawerLayout;
48 import android.support.v4.widget.SwipeRefreshLayout;
49 import android.support.v7.app.ActionBar;
50 import android.support.v7.app.ActionBarDrawerToggle;
51 import android.support.v7.app.AppCompatActivity;
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.ProgressBar;
71
72 import java.io.UnsupportedEncodingException;
73 import java.net.MalformedURLException;
74 import java.net.URL;
75 import java.net.URLEncoder;
76 import java.util.HashMap;
77 import java.util.Map;
78
79 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
80 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener,
81         SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener {
82
83     // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.
84     // It is also used in `onCreate()`.
85     public static ActionBar appBar;
86
87     // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
88     // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
89     public static Bitmap favoriteIcon;
90
91     // `formattedUrlString` is public static so it can be accessed from `BookmarksActivity`.
92     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
93     public static String formattedUrlString;
94
95     // `sslCertificate` is public static so it can be accessed from `ViewSslCertificate`.  It is also used in `onCreate()`.
96     public static SslCertificate sslCertificate;
97
98
99     // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `loadUrlFromTextBox()`.
100     private WebView mainWebView;
101
102     // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
103     private SwipeRefreshLayout swipeRefreshLayout;
104
105     // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, and `onRestart()`.
106     private CookieManager cookieManager;
107
108     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
109     private final Map<String, String> customHeaders = new HashMap<>();
110
111     // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`.
112     // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`.
113     private Boolean javaScriptEnabled;
114
115     // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
116     private boolean firstPartyCookiesEnabled;
117
118     // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
119     private boolean thirdPartyCookiesEnabled;
120
121     // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
122     private boolean domStorageEnabled;
123
124     // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
125     private boolean saveFormDataEnabled;
126
127     // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`.
128     private boolean swipeToRefreshEnabled;
129
130     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`.
131     private String homepage;
132
133     // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
134     private String javaScriptDisabledSearchURL;
135
136     // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
137     private String javaScriptEnabledSearchURL;
138
139     // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
140     private Menu mainMenu;
141
142     // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
143     private ActionBarDrawerToggle drawerToggle;
144
145     // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
146     private DrawerLayout drawerLayout;
147
148     // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
149     private EditText urlTextBox;
150
151     // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
152     private View adView;
153
154     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
155     private SslErrorHandler sslErrorHandler;
156
157     @Override
158     // 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.
159     @SuppressLint("SetJavaScriptEnabled")
160     protected void onCreate(Bundle savedInstanceState) {
161         super.onCreate(savedInstanceState);
162         setContentView(R.layout.main_coordinatorlayout);
163
164         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
165         Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
166         setSupportActionBar(supportAppBar);
167         appBar = getSupportActionBar();
168
169         // This is needed to get rid of the Android Studio warning that appBar might be null.
170         assert appBar != null;
171
172         // Add the custom url_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
173         appBar.setCustomView(R.layout.url_bar);
174         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
175
176         // Set the "go" button on the keyboard to load the URL in urlTextBox.
177         urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
178         urlTextBox.setOnKeyListener(new View.OnKeyListener() {
179             public boolean onKey(View v, int keyCode, KeyEvent event) {
180                 // If the event is a key-down event on the "enter" button, load the URL.
181                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
182                     // Load the URL into the mainWebView and consume the event.
183                     try {
184                         loadUrlFromTextBox();
185                     } catch (UnsupportedEncodingException e) {
186                         e.printStackTrace();
187                     }
188                     // If the enter key was pressed, consume the event.
189                     return true;
190                 } else {
191                     // If any other key was pressed, do not consume the event.
192                     return false;
193                 }
194             }
195         });
196
197         final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
198
199         // Implement swipe to refresh
200         swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
201         swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
202         swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
203             @Override
204             public void onRefresh() {
205                 mainWebView.reload();
206             }
207         });
208
209         mainWebView = (WebView) findViewById(R.id.mainWebView);
210
211         // Create the navigation drawer.
212         drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
213         // The DrawerTitle identifies the drawer in accessibility mode.
214         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
215
216         // Listen for touches on the navigation menu.
217         final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
218         navigationView.setNavigationItemSelectedListener(this);
219
220         // drawerToggle creates the hamburger icon at the start of the AppBar.
221         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
222
223         mainWebView.setWebViewClient(new WebViewClient() {
224             // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
225             // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
226             @SuppressWarnings("deprecation")
227             @Override
228             public boolean shouldOverrideUrlLoading(WebView view, String url) {
229                 // Use an external email program if the link begins with "mailto:".
230                 if (url.startsWith("mailto:")) {
231                     // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
232                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
233
234                     // Parse the url and set it as the data for the `Intent`.
235                     emailIntent.setData(Uri.parse(url));
236
237                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
238                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
239
240                     // Make it so.
241                     startActivity(emailIntent);
242                     return true;
243                 } else {  // Load the URL in Privacy Browser.
244                     mainWebView.loadUrl(url, customHeaders);
245                     return true;
246                 }
247             }
248
249             // Update the URL in urlTextBox when the page starts to load.
250             @Override
251             public void onPageStarted(WebView view, String url, Bitmap favicon) {
252                 // 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.
253                 formattedUrlString = url;
254
255                 // Display the loading URL is the URL text box.
256                 urlTextBox.setText(url);
257             }
258
259             // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
260             @Override
261             public void onPageFinished(WebView view, String url) {
262                 formattedUrlString = url;
263
264                 // Only update urlTextBox if the user is not typing in it.
265                 if (!urlTextBox.hasFocus()) {
266                     urlTextBox.setText(formattedUrlString);
267                 }
268
269                 // Store the SSL certificate so it can be accessed from `ViewSslCertificate`.
270                 sslCertificate = mainWebView.getCertificate();
271             }
272
273             // Handle SSL Certificate errors.
274             @Override
275             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
276                 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
277                 sslErrorHandler = handler;
278
279                 // Display the SSL error `AlertDialog`.
280                 DialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
281                 sslCertificateErrorDialogFragment.show(getFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
282             }
283         });
284
285         mainWebView.setWebChromeClient(new WebChromeClient() {
286             // Update the progress bar when a page is loading.
287             @Override
288             public void onProgressChanged(WebView view, int progress) {
289                 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
290                 progressBar.setProgress(progress);
291                 if (progress < 100) {
292                     progressBar.setVisibility(View.VISIBLE);
293                 } else {
294                     progressBar.setVisibility(View.GONE);
295
296                     //Stop the `SwipeToRefresh` indicator if it is running
297                     swipeRefreshLayout.setRefreshing(false);
298                 }
299             }
300
301             // Set the favorite icon when it changes.
302             @Override
303             public void onReceivedIcon(WebView view, Bitmap icon) {
304                 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
305                 favoriteIcon = icon;
306
307                 // Place the favorite icon in the appBar.
308                 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
309                 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
310             }
311
312             // Enter full screen video
313             @Override
314             public void onShowCustomView(View view, CustomViewCallback callback) {
315                 appBar.hide();
316
317                 // Show the fullScreenVideoFrameLayout.
318                 fullScreenVideoFrameLayout.addView(view);
319                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
320
321                 // Hide the mainWebView.
322                 mainWebView.setVisibility(View.GONE);
323
324                 // Hide the ad if this is the free flavor.
325                 BannerAd.hideAd(adView);
326
327                 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
328                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
329                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
330                  */
331                 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
332             }
333
334             // Exit full screen video
335             public void onHideCustomView() {
336                 appBar.show();
337
338                 // Show the mainWebView.
339                 mainWebView.setVisibility(View.VISIBLE);
340
341                 // Show the ad if this is the free flavor.
342                 BannerAd.showAd(adView);
343
344                 // Hide the fullScreenVideoFrameLayout.
345                 fullScreenVideoFrameLayout.removeAllViews();
346                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
347             }
348         });
349
350         // Allow the downloading of files.
351         mainWebView.setDownloadListener(new DownloadListener() {
352             @Override
353             public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
354                 // Show the `DownloadFile` `AlertDialog` and name this instance `@string/download`.
355                 DialogFragment downloadFileDialogFragment = DownloadFile.fromUrl(url, contentDisposition, contentLength);
356                 downloadFileDialogFragment.show(getFragmentManager(), getResources().getString(R.string.download));
357             }
358         });
359
360         // Allow pinch to zoom.
361         mainWebView.getSettings().setBuiltInZoomControls(true);
362
363         // Hide zoom controls.
364         mainWebView.getSettings().setDisplayZoomControls(false);
365
366         // Initialize cookieManager.
367         cookieManager = CookieManager.getInstance();
368
369         // 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).
370         customHeaders.put("X-Requested-With", "");
371
372         // Initialize the default preference values the first time the program is run.
373         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
374
375         // Apply the settings from the shared preferences.
376         applySettings();
377
378         // Get the intent information that started the app.
379         final Intent intent = getIntent();
380
381         if (intent.getData() != null) {
382             // Get the intent data and convert it to a string.
383             final Uri intentUriData = intent.getData();
384             formattedUrlString = intentUriData.toString();
385         }
386
387         // If formattedUrlString is null assign the homepage to it.
388         if (formattedUrlString == null) {
389             formattedUrlString = homepage;
390         }
391
392         // Load the initial website.
393         mainWebView.loadUrl(formattedUrlString, customHeaders);
394
395         // If the favorite icon is null, load the default.
396         if (favoriteIcon == null) {
397             // We have to use `ContextCompat` until API >= 21.
398             Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
399             BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
400             favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
401         }
402
403         // Initialize AdView for the free flavor and request an ad.  If this is not the free flavor BannerAd.requestAd() does nothing.
404         adView = findViewById(R.id.adView);
405         BannerAd.requestAd(adView);
406     }
407
408
409     @Override
410     protected void onNewIntent(Intent intent) {
411         // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
412         setIntent(intent);
413
414         if (intent.getData() != null) {
415             // Get the intent data and convert it to a string.
416             final Uri intentUriData = intent.getData();
417             formattedUrlString = intentUriData.toString();
418         }
419
420         // Close the navigation drawer if it is open.
421         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
422             drawerLayout.closeDrawer(GravityCompat.START);
423         }
424
425         // Load the website.
426         mainWebView.loadUrl(formattedUrlString, customHeaders);
427
428         // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
429         mainWebView.requestFocus();
430     }
431
432     @Override
433     public boolean onCreateOptionsMenu(Menu menu) {
434         // Inflate the menu; this adds items to the action bar if it is present.
435         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
436
437         // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
438         mainMenu = menu;
439
440         // Set the initial status of the privacy icons.
441         updatePrivacyIcons();
442
443         // Get handles for the menu items.
444         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
445         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
446         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
447         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
448
449         // Only display third-Party Cookies if SDK >= 21
450         toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21);
451
452         // Get the shared preference values.  `this` references the current context.
453         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
454
455         // Set the status of the additional app bar icons.  The default is `false`.
456         if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
457             toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
458             toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
459             toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
460         } else { //Do not display the additional icons.
461             toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
462             toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
463             toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
464         }
465
466         return true;
467     }
468
469     @Override
470     public boolean onPrepareOptionsMenu(Menu menu) {
471         // Get handles for the menu items.
472         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
473         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
474         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
475         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
476         MenuItem clearCookies = menu.findItem(R.id.clearCookies);
477         MenuItem clearFormData = menu.findItem(R.id.clearFormData);
478         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
479
480         // Set the status of the menu item checkboxes.
481         toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
482         toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
483         toggleDomStorage.setChecked(domStorageEnabled);
484         toggleSaveFormData.setChecked(saveFormDataEnabled);
485
486         // Enable third-party cookies if first-party cookies are enabled.
487         toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled);
488
489         // Enable DOM Storage if JavaScript is enabled.
490         toggleDomStorage.setEnabled(javaScriptEnabled);
491
492         // Enable Clear Cookies if there are any.
493         clearCookies.setEnabled(cookieManager.hasCookies());
494
495         // Enable Clear Form Data is there is any.
496         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
497         clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
498
499         // Only show `Refresh` if `swipeToRefresh` is disabled.
500         refreshMenuItem.setVisible(!swipeToRefreshEnabled);
501
502         // Initialize font size variables.
503         int fontSize = mainWebView.getSettings().getTextZoom();
504         String fontSizeTitle;
505         MenuItem selectedFontSizeMenuItem;
506
507         // Prepare the font size title and current size menu item.
508         switch (fontSize) {
509             case 50:
510                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
511                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
512                 break;
513
514             case 75:
515                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
516                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
517                 break;
518
519             case 100:
520                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
521                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
522                 break;
523
524             case 125:
525                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
526                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
527                 break;
528
529             case 150:
530                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
531                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
532                 break;
533
534             case 175:
535                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
536                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
537                 break;
538
539             case 200:
540                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
541                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
542                 break;
543
544             default:
545                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
546                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
547                 break;
548         }
549
550         // Set the font size title and select the current size menu item.
551         MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
552         fontSizeMenuItem.setTitle(fontSizeTitle);
553         selectedFontSizeMenuItem.setChecked(true);
554
555         // Run all the other default commands.
556         super.onPrepareOptionsMenu(menu);
557
558         // `return true` displays the menu.
559         return true;
560     }
561
562     @Override
563     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
564     @SuppressLint("SetJavaScriptEnabled")
565     // removeAllCookies is deprecated, but it is required for API < 21.
566     @SuppressWarnings("deprecation")
567     public boolean onOptionsItemSelected(MenuItem menuItem) {
568         int menuItemId = menuItem.getItemId();
569
570         // Set the commands that relate to the menu entries.
571         switch (menuItemId) {
572             case R.id.toggleJavaScript:
573                 // Switch the status of javaScriptEnabled.
574                 javaScriptEnabled = !javaScriptEnabled;
575
576                 // Apply the new JavaScript status.
577                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
578
579                 // Update the privacy icon.
580                 updatePrivacyIcons();
581
582                 // Display a `Snackbar`.
583                 if (javaScriptEnabled) {  // JavaScrip is enabled.
584                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
585                 } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
586                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
587                 } else {  // Privacy mode.
588                     Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
589                 }
590
591                 // Reload the WebView.
592                 mainWebView.reload();
593                 return true;
594
595             case R.id.toggleFirstPartyCookies:
596                 // Switch the status of firstPartyCookiesEnabled.
597                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
598
599                 // Update the menu checkbox.
600                 menuItem.setChecked(firstPartyCookiesEnabled);
601
602                 // Apply the new cookie status.
603                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
604
605                 // Update the privacy icon.
606                 updatePrivacyIcons();
607
608                 // Display a `Snackbar`.
609                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
610                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
611                 } else if (javaScriptEnabled){  // JavaScript is still enabled.
612                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
613                 } else {  // Privacy mode.
614                     Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
615                 }
616
617                 // Reload the WebView.
618                 mainWebView.reload();
619                 return true;
620
621             case R.id.toggleThirdPartyCookies:
622                 if (Build.VERSION.SDK_INT >= 21) {
623                     // Switch the status of thirdPartyCookiesEnabled.
624                     thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
625
626                     // Update the menu checkbox.
627                     menuItem.setChecked(thirdPartyCookiesEnabled);
628
629                     // Apply the new cookie status.
630                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
631
632                     // Display a `Snackbar`.
633                     if (thirdPartyCookiesEnabled) {
634                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
635                     } else {
636                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
637                     }
638
639                     // Reload the WebView.
640                     mainWebView.reload();
641                 } // Else do nothing because SDK < 21.
642                 return true;
643
644             case R.id.toggleDomStorage:
645                 // Switch the status of domStorageEnabled.
646                 domStorageEnabled = !domStorageEnabled;
647
648                 // Update the menu checkbox.
649                 menuItem.setChecked(domStorageEnabled);
650
651                 // Apply the new DOM Storage status.
652                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
653
654                 // Display a `Snackbar`.
655                 if (domStorageEnabled) {
656                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
657                 } else {
658                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
659                 }
660
661                 // Reload the WebView.
662                 mainWebView.reload();
663                 return true;
664
665             case R.id.toggleSaveFormData:
666                 // Switch the status of saveFormDataEnabled.
667                 saveFormDataEnabled = !saveFormDataEnabled;
668
669                 // Update the menu checkbox.
670                 menuItem.setChecked(saveFormDataEnabled);
671
672                 // Apply the new form data status.
673                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
674
675                 // Display a `Snackbar`.
676                 if (saveFormDataEnabled) {
677                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
678                 } else {
679                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
680                 }
681
682                 // Reload the WebView.
683                 mainWebView.reload();
684                 return true;
685
686             case R.id.clearCookies:
687                 if (Build.VERSION.SDK_INT < 21) {
688                     cookieManager.removeAllCookie();
689                 } else {
690                     cookieManager.removeAllCookies(null);
691                 }
692                 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
693                 return true;
694
695             case R.id.clearDomStorage:
696                 WebStorage webStorage = WebStorage.getInstance();
697                 webStorage.deleteAllData();
698                 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
699                 return true;
700
701             case R.id.clearFormData:
702                 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
703                 mainWebViewDatabase.clearFormData();
704                 mainWebView.reload();
705                 return true;
706
707             case R.id.fontSizeFiftyPercent:
708                 mainWebView.getSettings().setTextZoom(50);
709                 return true;
710
711             case R.id.fontSizeSeventyFivePercent:
712                 mainWebView.getSettings().setTextZoom(75);
713                 return true;
714
715             case R.id.fontSizeOneHundredPercent:
716                 mainWebView.getSettings().setTextZoom(100);
717                 return true;
718
719             case R.id.fontSizeOneHundredTwentyFivePercent:
720                 mainWebView.getSettings().setTextZoom(125);
721                 return true;
722
723             case R.id.fontSizeOneHundredFiftyPercent:
724                 mainWebView.getSettings().setTextZoom(150);
725                 return true;
726
727             case R.id.fontSizeOneHundredSeventyFivePercent:
728                 mainWebView.getSettings().setTextZoom(175);
729                 return true;
730
731             case R.id.fontSizeTwoHundredPercent:
732                 mainWebView.getSettings().setTextZoom(200);
733                 return true;
734
735             case R.id.share:
736                 Intent shareIntent = new Intent();
737                 shareIntent.setAction(Intent.ACTION_SEND);
738                 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
739                 shareIntent.setType("text/plain");
740                 startActivity(Intent.createChooser(shareIntent, "Share URL"));
741                 return true;
742
743             case R.id.addToHomescreen:
744                 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `@string/create_shortcut`.
745                 DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
746                 createHomeScreenShortcutDialogFragment.show(getFragmentManager(), getResources().getString(R.string.create_shortcut));
747
748                 //Everything else will be handled by `CreateHomeScreenShortcut` and the associated listener below.
749                 return true;
750
751             case R.id.print:
752                 // Get a `PrintManager` instance.
753                 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
754
755                 // Convert `mainWebView` to `printDocumentAdapter`.
756                 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
757
758                 // Print the document.  The print attributes are `null`.
759                 printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
760                 return true;
761
762             case R.id.refresh:
763                 mainWebView.reload();
764                 return true;
765
766             default:
767                 // Don't consume the event.
768                 return super.onOptionsItemSelected(menuItem);
769         }
770     }
771
772     // removeAllCookies is deprecated, but it is required for API < 21.
773     @SuppressWarnings("deprecation")
774     @Override
775     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
776         int menuItemId = menuItem.getItemId();
777
778         switch (menuItemId) {
779             case R.id.home:
780                 mainWebView.loadUrl(homepage, customHeaders);
781                 break;
782
783             case R.id.back:
784                 if (mainWebView.canGoBack()) {
785                     mainWebView.goBack();
786                 }
787                 break;
788
789             case R.id.forward:
790                 if (mainWebView.canGoForward()) {
791                     mainWebView.goForward();
792                 }
793                 break;
794
795             case R.id.bookmarks:
796                 // Launch BookmarksActivity.
797                 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
798                 startActivity(bookmarksIntent);
799                 break;
800
801             case R.id.downloads:
802                 // Launch the system Download Manager.
803                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
804
805                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
806                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
807
808                 startActivity(downloadManagerIntent);
809                 break;
810
811             case R.id.settings:
812                 // Launch `SettingsActivity`.
813                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
814                 startActivity(settingsIntent);
815                 break;
816
817             case R.id.guide:
818                 // Launch `GuideActivity`.
819                 Intent guideIntent = new Intent(this, GuideActivity.class);
820                 startActivity(guideIntent);
821                 break;
822
823             case R.id.about:
824                 // Launch `AboutActivity`.
825                 Intent aboutIntent = new Intent(this, AboutActivity.class);
826                 startActivity(aboutIntent);
827                 break;
828
829             case R.id.clearAndExit:
830                 // Clear cookies.  The commands changed slightly in API 21.
831                 if (Build.VERSION.SDK_INT >= 21) {
832                     cookieManager.removeAllCookies(null);
833                 } else {
834                     cookieManager.removeAllCookie();
835                 }
836
837                 // Clear DOM storage.
838                 WebStorage domStorage = WebStorage.getInstance();
839                 domStorage.deleteAllData();
840
841                 // Clear form data.
842                 WebViewDatabase formData = WebViewDatabase.getInstance(this);
843                 formData.clearFormData();
844
845                 // Clear cache.  The argument of "true" includes disk files.
846                 mainWebView.clearCache(true);
847
848                 // Clear the back/forward history.
849                 mainWebView.clearHistory();
850
851                 // Clear any SSL certificate preferences.
852                 mainWebView.clearSslPreferences();
853
854                 // Clear `formattedUrlString`.
855                 formattedUrlString = null;
856
857                 // Clear `customHeaders`.
858                 customHeaders.clear();
859
860                 // Destroy the internal state of the webview.
861                 mainWebView.destroy();
862
863                 // Close Privacy Browser.  finishAndRemoveTask also removes Privacy Browser from the recent app list.
864                 if (Build.VERSION.SDK_INT >= 21) {
865                     finishAndRemoveTask();
866                 } else {
867                     finish();
868                 }
869                 break;
870
871             default:
872                 break;
873         }
874
875         // Close the navigation drawer.
876         drawerLayout.closeDrawer(GravityCompat.START);
877         return true;
878     }
879
880     @Override
881     public void onPostCreate(Bundle savedInstanceState) {
882         super.onPostCreate(savedInstanceState);
883
884         // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
885         drawerToggle.syncState();
886     }
887
888     @Override
889     public void onConfigurationChanged(Configuration newConfig) {
890         super.onConfigurationChanged(newConfig);
891
892         // Reload the ad if this is the free flavor.
893         BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
894
895         // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
896         adView = findViewById(R.id.adView);
897
898         // `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
899         invalidateOptionsMenu();
900     }
901
902     @Override
903     public void onCreateHomeScreenShortcut(DialogFragment dialogFragment) {
904         // Get shortcutNameEditText from the alert dialog.
905         EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
906
907         // Create the bookmark shortcut based on formattedUrlString.
908         Intent bookmarkShortcut = new Intent();
909         bookmarkShortcut.setAction(Intent.ACTION_VIEW);
910         bookmarkShortcut.setData(Uri.parse(formattedUrlString));
911
912         // Place the bookmark shortcut on the home screen.
913         Intent placeBookmarkShortcut = new Intent();
914         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
915         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
916         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
917         placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
918         sendBroadcast(placeBookmarkShortcut);
919     }
920
921     @Override
922     public void onDownloadFile(DialogFragment dialogFragment, String downloadUrl) {
923         DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
924         DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
925
926         // Get the file name from `dialogFragment`.
927         EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
928         String fileName = downloadFileNameEditText.getText().toString();
929
930         // Set the download save in the the `DIRECTORY_DOWNLOADS`using `fileName`.
931         // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
932         downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
933
934         // Allow `MediaScanner` to index the download if it is a media file.
935         downloadRequest.allowScanningByMediaScanner();
936
937         // Add the URL as the description for the download.
938         downloadRequest.setDescription(downloadUrl);
939
940         // Show the download notification after the download is completed.
941         downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
942
943         // Initiate the download and display a Snackbar.
944         downloadManager.enqueue(downloadRequest);
945     }
946
947     public void viewSslCertificate(View view) {
948         // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
949         DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
950         viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
951     }
952
953     @Override
954     public void onSslErrorCancel() {
955         sslErrorHandler.cancel();
956     }
957
958     @Override
959     public void onSslErrorProceed() {
960         sslErrorHandler.proceed();
961     }
962
963     // Override onBackPressed to handle the navigation drawer and mainWebView.
964     @Override
965     public void onBackPressed() {
966         final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
967
968         // Close the navigation drawer if it is available.  GravityCompat.START is the drawer on the left on Left-to-Right layout text.
969         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
970             drawerLayout.closeDrawer(GravityCompat.START);
971         } else {
972             // Load the previous URL if available.
973             if (mainWebView.canGoBack()) {
974                 mainWebView.goBack();
975             } else {
976                 // Pass onBackPressed to the system.
977                 super.onBackPressed();
978             }
979         }
980     }
981
982     @Override
983     public void onPause() {
984         // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
985         BannerAd.pauseAd(adView);
986
987         super.onPause();
988     }
989
990     @Override
991     public void onResume() {
992         super.onResume();
993
994         // We need to resume the adView for the free flavor.
995         BannerAd.resumeAd(adView);
996     }
997
998     @Override
999     public void onRestart() {
1000         super.onRestart();
1001
1002         // Apply the settings from shared preferences, which might have been changed in `SettingsActivity`.
1003         applySettings();
1004
1005         // Update the privacy icons.
1006         updatePrivacyIcons();
1007
1008     }
1009
1010     private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1011         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
1012         String unformattedUrlString = urlTextBox.getText().toString().trim();
1013
1014         URL unformattedUrl = null;
1015         Uri.Builder formattedUri = new Uri.Builder();
1016
1017         // Check to see if unformattedUrlString is a valid URL.  Otherwise, convert it into a Duck Duck Go search.
1018         if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1019             // Add http:// at the beginning if it is missing.  Otherwise the app will segfault.
1020             if (!unformattedUrlString.startsWith("http")) {
1021                 unformattedUrlString = "http://" + unformattedUrlString;
1022             }
1023
1024             // 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.
1025             try {
1026                 unformattedUrl = new URL(unformattedUrlString);
1027             } catch (MalformedURLException e) {
1028                 e.printStackTrace();
1029             }
1030
1031             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1032             final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1033             final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1034             final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1035             final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1036             final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1037
1038             formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1039             formattedUrlString = formattedUri.build().toString();
1040         } else {
1041             // Sanitize the search input and convert it to a DuckDuckGo search.
1042             final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1043
1044             // Use the correct search URL.
1045             if (javaScriptEnabled) {  // JavaScript is enabled.
1046                 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1047             } else { // JavaScript is disabled.
1048                 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1049             }
1050         }
1051
1052         mainWebView.loadUrl(formattedUrlString, customHeaders);
1053
1054         // Hides the keyboard so we can see the webpage.
1055         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
1056         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1057     }
1058
1059     private void applySettings() {
1060         // Get the shared preference values.  `this` references the current context.
1061         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1062
1063         // Store the values from `sharedPreferences` in variables.
1064         String userAgentString = sharedPreferences.getString("user_agent", "Default user agent");
1065         String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
1066         String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
1067         String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
1068         String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
1069         String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
1070         String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
1071         String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
1072         swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
1073         boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true);
1074         boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
1075
1076         // Because they can be modified on-the-fly by the user, these default settings are only applied when the program first runs.
1077         if (javaScriptEnabled == null) {  // If `javaScriptEnabled` is null the program is just starting.
1078             // Get the values from `sharedPreferences`.
1079             javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
1080             firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
1081             thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
1082             domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
1083             saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
1084
1085             // Apply the default settings.
1086             mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
1087             cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1088             mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1089             mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1090             mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
1091
1092             // Set third-party cookies status if API >= 21.
1093             if (Build.VERSION.SDK_INT >= 21) {
1094                 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1095             }
1096         }
1097
1098         // Apply the other settings from `sharedPreferences`.
1099         homepage = homepageString;
1100         swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
1101
1102         // Set the user agent initial status.
1103         switch (userAgentString) {
1104             case "Default user agent":
1105                 // Set the user agent to `""`, which uses the default value.
1106                 mainWebView.getSettings().setUserAgentString("");
1107                 break;
1108
1109             case "Custom user agent":
1110                 // Set the custom user agent.
1111                 mainWebView.getSettings().setUserAgentString(customUserAgentString);
1112                 break;
1113
1114             default:
1115                 // Use the selected user agent.
1116                 mainWebView.getSettings().setUserAgentString(userAgentString);
1117                 break;
1118         }
1119
1120         // Set JavaScript disabled search.
1121         if (javaScriptDisabledSearchString.equals("Custom URL")) {  // Get the custom URL string.
1122             javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString;
1123         } else {  // Use the string from the pre-built list.
1124             javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
1125         }
1126
1127         // Set JavaScript enabled search.
1128         if (javaScriptEnabledSearchString.equals("Custom URL")) {  // Get the custom URL string.
1129             javaScriptEnabledSearchURL = javaScriptEnabledCustomSearchString;
1130         } else {  // Use the string from the pre-built list.
1131             javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
1132         }
1133
1134         // Set Do Not Track status.
1135         if (doNotTrackEnabled) {
1136             customHeaders.put("DNT", "1");
1137         } else {
1138             customHeaders.remove("DNT");
1139         }
1140
1141         // Set Orbot proxy status.
1142         if (proxyThroughOrbot) {
1143             // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
1144             OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
1145         } else {  // Reset the proxy to default.  The host is `""` and the port is `"0"`.
1146             OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
1147         }
1148     }
1149
1150     private void updatePrivacyIcons() {
1151         // Get handles for the icons.
1152         MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
1153         MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
1154         MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
1155         MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
1156
1157         // Update `privacyIcon`.
1158         if (javaScriptEnabled) {  // JavaScript is enabled.
1159             privacyIcon.setIcon(R.drawable.javascript_enabled);
1160         } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
1161             privacyIcon.setIcon(R.drawable.warning);
1162         } else {  // All the dangerous features are disabled.
1163             privacyIcon.setIcon(R.drawable.privacy_mode);
1164         }
1165
1166         // Update `firstPartyCookiesIcon`.
1167         if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
1168             firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled);
1169         } else {  // First-party cookies are disabled.
1170             firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1171         }
1172
1173         // Update `domStorageIcon`.
1174         if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
1175             domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
1176         } else if (javaScriptEnabled){  // JavaScript is enabled but DOM storage is disabled.
1177             domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
1178         } else {  // JavaScript is disabled, so DOM storage is ghosted.
1179             domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
1180         }
1181
1182         // Update `formDataIcon`.
1183         if (saveFormDataEnabled) {  // Form data is enabled.
1184             formDataIcon.setIcon(R.drawable.form_data_enabled);
1185         } else {  // Form data is disabled.
1186             formDataIcon.setIcon(R.drawable.form_data_disabled);
1187         }
1188
1189         // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.
1190         // `this` references the current activity.
1191         ActivityCompat.invalidateOptionsMenu(this);
1192     }
1193 }