]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/activities/MainWebView.java
Ad a preference to disable full screen browsing mode. Fixes https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / activities / MainWebView.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.activities;
21
22 import android.annotation.SuppressLint;
23 import android.app.DialogFragment;
24 import android.app.DownloadManager;
25 import android.content.ClipData;
26 import android.content.ClipboardManager;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.SharedPreferences;
30 import android.content.res.Configuration;
31 import android.graphics.Bitmap;
32 import android.graphics.drawable.BitmapDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.net.Uri;
35 import android.net.http.SslCertificate;
36 import android.net.http.SslError;
37 import android.os.Build;
38 import android.os.Bundle;
39 import android.preference.PreferenceManager;
40 import android.print.PrintDocumentAdapter;
41 import android.print.PrintManager;
42 import android.support.annotation.NonNull;
43 import android.support.design.widget.CoordinatorLayout;
44 import android.support.design.widget.NavigationView;
45 import android.support.design.widget.Snackbar;
46 import android.support.v4.app.ActivityCompat;
47 import android.support.v4.content.ContextCompat;
48 import android.support.v4.view.GravityCompat;
49 import android.support.v4.widget.DrawerLayout;
50 import android.support.v4.widget.SwipeRefreshLayout;
51 import android.support.v7.app.ActionBar;
52 import android.support.v7.app.ActionBarDrawerToggle;
53 import android.support.v7.app.AppCompatActivity;
54 import android.support.v7.app.AppCompatDialogFragment;
55 import android.support.v7.widget.Toolbar;
56 import android.text.Editable;
57 import android.text.TextWatcher;
58 import android.util.Patterns;
59 import android.view.ContextMenu;
60 import android.view.GestureDetector;
61 import android.view.KeyEvent;
62 import android.view.Menu;
63 import android.view.MenuItem;
64 import android.view.MotionEvent;
65 import android.view.View;
66 import android.view.WindowManager;
67 import android.view.inputmethod.InputMethodManager;
68 import android.webkit.CookieManager;
69 import android.webkit.DownloadListener;
70 import android.webkit.SslErrorHandler;
71 import android.webkit.WebBackForwardList;
72 import android.webkit.WebChromeClient;
73 import android.webkit.WebStorage;
74 import android.webkit.WebView;
75 import android.webkit.WebViewClient;
76 import android.webkit.WebViewDatabase;
77 import android.widget.EditText;
78 import android.widget.FrameLayout;
79 import android.widget.ImageView;
80 import android.widget.LinearLayout;
81 import android.widget.ProgressBar;
82 import android.widget.RelativeLayout;
83 import android.widget.TextView;
84
85 import com.stoutner.privacybrowser.BannerAd;
86 import com.stoutner.privacybrowser.BuildConfig;
87 import com.stoutner.privacybrowser.R;
88 import com.stoutner.privacybrowser.helpers.OrbotProxyHelper;
89 import com.stoutner.privacybrowser.dialogs.CreateHomeScreenShortcut;
90 import com.stoutner.privacybrowser.dialogs.DownloadFile;
91 import com.stoutner.privacybrowser.dialogs.DownloadImage;
92 import com.stoutner.privacybrowser.dialogs.SslCertificateError;
93 import com.stoutner.privacybrowser.dialogs.UrlHistory;
94 import com.stoutner.privacybrowser.dialogs.ViewSslCertificate;
95
96 import java.io.UnsupportedEncodingException;
97 import java.net.MalformedURLException;
98 import java.net.URL;
99 import java.net.URLEncoder;
100 import java.util.HashMap;
101 import java.util.Map;
102
103 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
104 public class MainWebView extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener,
105         SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener, DownloadImage.DownloadImageListener, UrlHistory.UrlHistoryListener {
106
107     // `appBar` is public static so it can be accessed from `OrbotProxyHelper`.
108     // It is also used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
109     public static ActionBar appBar;
110
111     // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `Bookmarks`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
112     // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
113     public static Bitmap favoriteIcon;
114
115     // `formattedUrlString` is public static so it can be accessed from `Bookmarks`.
116     // It is also used in `onCreate()`, `onOptionsItemSelected()`, `onCreateHomeScreenShortcutCreate()`, and `loadUrlFromTextBox()`.
117     public static String formattedUrlString;
118
119     // `sslCertificate` is public static so it can be accessed from `ViewSslCertificate`.  It is also used in `onCreate()`.
120     public static SslCertificate sslCertificate;
121
122
123
124     // `drawerLayout` is used in `onCreate()`, `onNewIntent()`, and `onBackPressed()`.
125     private DrawerLayout drawerLayout;
126
127     // `rootCoordinatorLayout` is used in `onCreate()` and `applySettings()`.
128     private CoordinatorLayout rootCoordinatorLayout;
129
130     // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`.
131     private WebView mainWebView;
132
133     // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`.
134     private EditText findOnPageEditText;
135
136     // `fullScreenVideoFrameLayout` is used in `onCreate()` and `onConfigurationChanged()`.
137     private FrameLayout fullScreenVideoFrameLayout;
138
139     // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`.
140     private SwipeRefreshLayout swipeRefreshLayout;
141
142     // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, and `onRestart()`.
143     private CookieManager cookieManager;
144
145     // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrlFromTextBox()`.
146     private final Map<String, String> customHeaders = new HashMap<>();
147
148     // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`.
149     // It is `Boolean` instead of `boolean` because `applySettings()` needs to know if it is `null`.
150     private Boolean javaScriptEnabled;
151
152     // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
153     private boolean firstPartyCookiesEnabled;
154
155     // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
156     private boolean thirdPartyCookiesEnabled;
157
158     // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
159     private boolean domStorageEnabled;
160
161     // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applySettings()`.
162     private boolean saveFormDataEnabled;
163
164     // `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applySettings()`.
165     private boolean swipeToRefreshEnabled;
166
167     // 'homepage' is used in `onCreate()`, `onNavigationItemSelected()`, and `applySettings()`.
168     private String homepage;
169
170     // `javaScriptDisabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
171     private String javaScriptDisabledSearchURL;
172
173     // `javaScriptEnabledSearchURL` is used in `loadURLFromTextBox()` and `applySettings()`.
174     private String javaScriptEnabledSearchURL;
175
176     // `fullScreenBrowsingModeEnabled` is used in `onCreate()` and `applySettings()`.
177     private boolean fullScreenBrowsingModeEnabled;
178
179     // `hideSystemBarsOnFullscreen` is used in `onCreate()` and `applySettings()`.
180     private boolean hideSystemBarsOnFullscreen;
181
182     // `translucentNavigationBarOnFullscreen` is used in `onCreate()` and `applySettings()`.
183     private boolean translucentNavigationBarOnFullscreen;
184
185     // `mainMenu` is used in `onCreateOptionsMenu()` and `updatePrivacyIcons()`.
186     private Menu mainMenu;
187
188     // `drawerToggle` is used in `onCreate()`, `onPostCreate()`, `onConfigurationChanged()`, `onNewIntent()`, and `onNavigationItemSelected()`.
189     private ActionBarDrawerToggle drawerToggle;
190
191     // `urlTextBox` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`.
192     private EditText urlTextBox;
193
194     // `adView` is used in `onCreate()` and `onConfigurationChanged()`.
195     private View adView;
196
197     // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`.
198     private SslErrorHandler sslErrorHandler;
199
200     // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`.
201     private InputMethodManager inputMethodManager;
202
203     @Override
204     // 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.
205     @SuppressLint("SetJavaScriptEnabled")
206     protected void onCreate(Bundle savedInstanceState) {
207         super.onCreate(savedInstanceState);
208         setContentView(R.layout.drawerlayout);
209
210         // Get a handle for `inputMethodManager`.
211         inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
212
213         // We need to use the `SupportActionBar` from `android.support.v7.app.ActionBar` until the minimum API is >= 21.
214         Toolbar supportAppBar = (Toolbar) findViewById(R.id.app_bar);
215         setSupportActionBar(supportAppBar);
216         appBar = getSupportActionBar();
217
218         // This is needed to get rid of the Android Studio warning that `appBar` might be null.
219         assert appBar != null;
220
221         // Add the custom url_app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
222         appBar.setCustomView(R.layout.url_app_bar);
223         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
224
225         // Set the "go" button on the keyboard to load the URL in urlTextBox.
226         urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
227         urlTextBox.setOnKeyListener(new View.OnKeyListener() {
228             @Override
229             public boolean onKey(View v, int keyCode, KeyEvent event) {
230                 // If the event is a key-down event on the `enter` button, load the URL.
231                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
232                     // Load the URL into the mainWebView and consume the event.
233                     try {
234                         loadUrlFromTextBox();
235                     } catch (UnsupportedEncodingException e) {
236                         e.printStackTrace();
237                     }
238                     // If the enter key was pressed, consume the event.
239                     return true;
240                 } else {
241                     // If any other key was pressed, do not consume the event.
242                     return false;
243                 }
244             }
245         });
246
247         // Get handles for views that need to be accessed.
248         drawerLayout = (DrawerLayout) findViewById(R.id.drawerlayout);
249         rootCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.root_coordinatorlayout);
250         mainWebView = (WebView) findViewById(R.id.mainWebView);
251         findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext);
252         fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.full_screen_video_framelayout);
253
254         // Create a double-tap listener to toggle full-screen mode.
255         final GestureDetector gestureDetector = new GestureDetector(this, new GestureDetector.SimpleOnGestureListener() {
256             // Override `onDoubleTap()`.  All other events are handled using the default settings.
257             @Override
258             public boolean onDoubleTap(MotionEvent event) {
259                 if (fullScreenBrowsingModeEnabled) {  // Only process the double-tap if full screen browsing mode is enabled.
260                     if (appBar.isShowing()) {  // If `appBar` is visible, switch to full screen mode.
261                         // Hide the `appBar`.
262                         appBar.hide();
263
264                         // Hide the `BannerAd` in the free flavor.
265                         if (BuildConfig.FLAVOR.contentEquals("free")) {
266                             BannerAd.hideAd(adView);
267                         }
268
269                         // Modify the system bars.
270                         if (hideSystemBarsOnFullscreen) {  // Hide everything.
271                             // Remove the translucent overlays.
272                             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
273
274                             // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
275                             drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
276
277                         /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
278                          * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
279                          * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
280                          */
281                             rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
282
283                             // Set `rootCoordinatorLayout` to fill the whole screen.
284                             rootCoordinatorLayout.setFitsSystemWindows(false);
285                         } else {  // Hide everything except the status and navigation bars.
286                             // Set `rootCoordinatorLayout` to fit under the status and navigation bars.
287                             rootCoordinatorLayout.setFitsSystemWindows(false);
288
289                             if (translucentNavigationBarOnFullscreen) {  // There is an Android Support Library bug that causes a scrim to print on the right side of the `Drawer Layout` when the navigation bar is displayed on the right of the screen.
290                                 // Set the navigation bar to be translucent.
291                                 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
292                             }
293                         }
294                     } else {  // Switch to normal viewing mode.
295                         // Show the `appBar`.
296                         appBar.show();
297
298                         // Show the `BannerAd` in the free flavor.
299                         if (BuildConfig.FLAVOR.contentEquals("free")) {
300                             // Reload the ad.  Because the screen may have rotated, we need to use `reloadAfterRotate`.
301                             BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
302
303                             // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
304                             adView = findViewById(R.id.adView);
305                         }
306
307                         // Remove the translucent navigation bar flag if it is set.
308                         getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
309
310                         // Add the translucent status flag if it is unset.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
311                         getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
312
313                         // Remove any `SYSTEM_UI` flags from `rootCoordinatorLayout`.
314                         rootCoordinatorLayout.setSystemUiVisibility(0);
315
316                         // Constrain `rootCoordinatorLayout` inside the status and navigation bars.
317                         rootCoordinatorLayout.setFitsSystemWindows(true);
318                     }
319
320                     // Consume the double-tap.
321                     return true;
322                 } else { // Do not consume the double-tap because full screen browsing mode is disabled.
323                     return false;
324                 }
325             }
326         });
327
328         // Pass all touch events on `mainWebView` through `gestureDetector` to check for double-taps.
329         mainWebView.setOnTouchListener(new View.OnTouchListener() {
330             @Override
331             public boolean onTouch(View v, MotionEvent event) {
332                 // Send the `event` to `gestureDetector`.
333                 return gestureDetector.onTouchEvent(event);
334             }
335         });
336
337         // Update `findOnPageCountTextView`.
338         mainWebView.setFindListener(new WebView.FindListener() {
339             // Get a handle for `findOnPageCountTextView`.
340             final TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview);
341
342             @Override
343             public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
344                 if ((isDoneCounting) && (numberOfMatches == 0)) {  // There are no matches.
345                     // Set `findOnPageCountTextView` to `0/0`.
346                     findOnPageCountTextView.setText(R.string.zero_of_zero);
347                 } else if (isDoneCounting) {  // There are matches.
348                     // `activeMatchOrdinal` is zero-based.
349                     int activeMatch = activeMatchOrdinal + 1;
350
351                     // Set `findOnPageCountTextView`.
352                     findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches);
353                 }
354             }
355         });
356
357         // Search for the string on the page whenever a character changes in the `findOnPageEditText`.
358         findOnPageEditText.addTextChangedListener(new TextWatcher() {
359             @Override
360             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
361                 // Do nothing.
362             }
363
364             @Override
365             public void onTextChanged(CharSequence s, int start, int before, int count) {
366                 // Do nothing.
367             }
368
369             @Override
370             public void afterTextChanged(Editable s) {
371                 // Search for the text in `mainWebView`.
372                 mainWebView.findAllAsync(findOnPageEditText.getText().toString());
373             }
374         });
375
376         // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard.
377         findOnPageEditText.setOnKeyListener(new View.OnKeyListener() {
378             @Override
379             public boolean onKey(View v, int keyCode, KeyEvent event) {
380                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {  // The `enter` key was pressed.
381                     // Hide the soft keyboard.  `0` indicates no additional flags.
382                     inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
383
384                     // Consume the event.
385                     return true;
386                 } else {  // A different key was pressed.
387                     // Do not consume the event.
388                     return false;
389                 }
390             }
391         });
392
393         // Implement swipe to refresh
394         swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
395         swipeRefreshLayout.setColorSchemeResources(R.color.blue_700);
396         swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
397             @Override
398             public void onRefresh() {
399                 mainWebView.reload();
400             }
401         });
402
403         // `DrawerTitle` identifies the `DrawerLayout` in accessibility mode.
404         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
405
406         // Listen for touches on the navigation menu.
407         final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationview);
408         navigationView.setNavigationItemSelectedListener(this);
409
410         // Get handles for `navigationMenu` and the back and forward menu items.  The menu is zero-based, so items 1, 2, and 3 are the second, third, and fourth entries in the menu.
411         final Menu navigationMenu = navigationView.getMenu();
412         final MenuItem navigationBackMenuItem = navigationMenu.getItem(1);
413         final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2);
414         final MenuItem navigationHistoryMenuItem = navigationMenu.getItem(3);
415
416         // The `DrawerListener` allows us to update the Navigation Menu.
417         drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
418             @Override
419             public void onDrawerSlide(View drawerView, float slideOffset) {
420             }
421
422             @Override
423             public void onDrawerOpened(View drawerView) {
424             }
425
426             @Override
427             public void onDrawerClosed(View drawerView) {
428             }
429
430             @Override
431             public void onDrawerStateChanged(int newState) {
432                 // Update the `Back`, `Forward`, and `History` menu items every time the drawer opens.
433                 navigationBackMenuItem.setEnabled(mainWebView.canGoBack());
434                 navigationForwardMenuItem.setEnabled(mainWebView.canGoForward());
435                 navigationHistoryMenuItem.setEnabled((mainWebView.canGoBack() || mainWebView.canGoForward()));
436
437                 // Hide the keyboard so we can see the navigation menu.  `0` indicates no additional flags.
438                 inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
439             }
440         });
441
442         // drawerToggle creates the hamburger icon at the start of the AppBar.
443         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation_drawer, R.string.close_navigation_drawer);
444
445         mainWebView.setWebViewClient(new WebViewClient() {
446             // `shouldOverrideUrlLoading` makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
447             // We have to use the deprecated `shouldOverrideUrlLoading` until API >= 24.
448             @SuppressWarnings("deprecation")
449             @Override
450             public boolean shouldOverrideUrlLoading(WebView view, String url) {
451                 // Use an external email program if the link begins with `mailto:`.
452                 if (url.startsWith("mailto:")) {
453                     // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
454                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
455
456                     // Parse the url and set it as the data for the `Intent`.
457                     emailIntent.setData(Uri.parse(url));
458
459                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
460                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
461
462                     // Make it so.
463                     startActivity(emailIntent);
464                     return true;
465                 } else {  // Load the URL in Privacy Browser.
466                     mainWebView.loadUrl(url, customHeaders);
467                     return true;
468                 }
469             }
470
471             // Update the URL in urlTextBox when the page starts to load.
472             @Override
473             public void onPageStarted(WebView view, String url, Bitmap favicon) {
474                 // 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.
475                 formattedUrlString = url;
476
477                 // Display the loading URL is the URL text box.
478                 urlTextBox.setText(url);
479             }
480
481             // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
482             @Override
483             public void onPageFinished(WebView view, String url) {
484                 formattedUrlString = url;
485
486                 // Only update urlTextBox if the user is not typing in it.
487                 if (!urlTextBox.hasFocus()) {
488                     urlTextBox.setText(formattedUrlString);
489                 }
490
491                 // Store the SSL certificate so it can be accessed from `ViewSslCertificate`.
492                 sslCertificate = mainWebView.getCertificate();
493             }
494
495             // Handle SSL Certificate errors.
496             @Override
497             public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
498                 // Store `handler` so it can be accesses from `onSslErrorCancel()` and `onSslErrorProceed()`.
499                 sslErrorHandler = handler;
500
501                 // Display the SSL error `AlertDialog`.
502                 AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error);
503                 sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error));
504             }
505         });
506
507         mainWebView.setWebChromeClient(new WebChromeClient() {
508             // Update the progress bar when a page is loading.
509             @Override
510             public void onProgressChanged(WebView view, int progress) {
511                 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
512                 progressBar.setProgress(progress);
513                 if (progress < 100) {
514                     progressBar.setVisibility(View.VISIBLE);
515                 } else {
516                     progressBar.setVisibility(View.GONE);
517
518                     //Stop the `SwipeToRefresh` indicator if it is running
519                     swipeRefreshLayout.setRefreshing(false);
520                 }
521             }
522
523             // Set the favorite icon when it changes.
524             @Override
525             public void onReceivedIcon(WebView view, Bitmap icon) {
526                 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
527                 favoriteIcon = icon;
528
529                 // Place the favorite icon in the appBar.
530                 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
531                 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
532             }
533
534             // Enter full screen video
535             @Override
536             public void onShowCustomView(View view, CustomViewCallback callback) {
537                 // Pause the ad if this is the free flavor.
538                 if (BuildConfig.FLAVOR.contentEquals("free")) {
539                     BannerAd.pauseAd(adView);
540                 }
541
542                 // Remove the translucent overlays.
543                 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
544
545                 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
546                 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
547
548                 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
549                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
550                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
551                  */
552                 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
553
554                 // Set `rootCoordinatorLayout` to fill the entire screen.
555                 rootCoordinatorLayout.setFitsSystemWindows(false);
556
557                 // Add `view` to `fullScreenVideoFrameLayout` and display it on the screen.
558                 fullScreenVideoFrameLayout.addView(view);
559                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
560             }
561
562             // Exit full screen video
563             public void onHideCustomView() {
564                 // Hide `fullScreenVideoFrameLayout`.
565                 fullScreenVideoFrameLayout.removeAllViews();
566                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
567
568                 // Add the translucent status flag.  This also resets `drawerLayout's` `View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN`.
569                 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
570
571                 // Set `rootCoordinatorLayout` to fit inside the status and navigation bars.  This also clears the `SYSTEM_UI` flags.
572                 rootCoordinatorLayout.setFitsSystemWindows(true);
573
574                 // Show the ad if this is the free flavor.
575                 if (BuildConfig.FLAVOR.contentEquals("free")) {
576                     // Reload the ad.  Because the screen may have rotated, we need to use `reloadAfterRotate`.
577                     BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
578
579                     // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
580                     adView = findViewById(R.id.adView);
581                 }
582             }
583         });
584
585         // Register `mainWebView` for a context menu.  This is used to see link targets and download images.
586         registerForContextMenu(mainWebView);
587
588         // Allow the downloading of files.
589         mainWebView.setDownloadListener(new DownloadListener() {
590             @Override
591             public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
592                 // Show the `DownloadFile` `AlertDialog` and name this instance `@string/download`.
593                 AppCompatDialogFragment downloadFileDialogFragment = DownloadFile.fromUrl(url, contentDisposition, contentLength);
594                 downloadFileDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
595             }
596         });
597
598         // Allow pinch to zoom.
599         mainWebView.getSettings().setBuiltInZoomControls(true);
600
601         // Hide zoom controls.
602         mainWebView.getSettings().setDisplayZoomControls(false);
603
604         // Initialize cookieManager.
605         cookieManager = CookieManager.getInstance();
606
607         // 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).
608         customHeaders.put("X-Requested-With", "");
609
610         // Initialize the default preference values the first time the program is run.  `this` is the context.  `false` keeps this command from resetting any current preferences back to default.
611         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
612
613         // Apply the settings from the shared preferences.
614         applySettings();
615
616         // Get the intent information that started the app.
617         final Intent intent = getIntent();
618
619         if (intent.getData() != null) {
620             // Get the intent data and convert it to a string.
621             final Uri intentUriData = intent.getData();
622             formattedUrlString = intentUriData.toString();
623         }
624
625         // If formattedUrlString is null assign the homepage to it.
626         if (formattedUrlString == null) {
627             formattedUrlString = homepage;
628         }
629
630         // Load the initial website.
631         mainWebView.loadUrl(formattedUrlString, customHeaders);
632
633         // If the favorite icon is null, load the default.
634         if (favoriteIcon == null) {
635             // We have to use `ContextCompat` until API >= 21.
636             Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
637             BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
638             favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
639         }
640
641         // Initialize AdView for the free flavor and request an ad.  If this is not the free flavor BannerAd.requestAd() does nothing.
642         adView = findViewById(R.id.adView);
643         BannerAd.requestAd(adView);
644     }
645
646
647     @Override
648     protected void onNewIntent(Intent intent) {
649         // Sets the new intent as the activity intent, so that any future `getIntent()`s pick up this one instead of creating a new activity.
650         setIntent(intent);
651
652         if (intent.getData() != null) {
653             // Get the intent data and convert it to a string.
654             final Uri intentUriData = intent.getData();
655             formattedUrlString = intentUriData.toString();
656         }
657
658         // Close the navigation drawer if it is open.
659         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
660             drawerLayout.closeDrawer(GravityCompat.START);
661         }
662
663         // Load the website.
664         mainWebView.loadUrl(formattedUrlString, customHeaders);
665
666         // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
667         mainWebView.requestFocus();
668     }
669
670     @Override
671     public boolean onCreateOptionsMenu(Menu menu) {
672         // Inflate the menu; this adds items to the action bar if it is present.
673         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
674
675         // Set mainMenu so it can be used by `onOptionsItemSelected()` and `updatePrivacyIcons`.
676         mainMenu = menu;
677
678         // Set the initial status of the privacy icons.  `false` does not call `invalidateOptionsMenu` as the last step.
679         updatePrivacyIcons(false);
680
681         // Get handles for the menu items.
682         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
683         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
684         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
685         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
686
687         // Only display third-Party Cookies if SDK >= 21
688         toggleThirdPartyCookies.setVisible(Build.VERSION.SDK_INT >= 21);
689
690         // Get the shared preference values.  `this` references the current context.
691         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
692
693         // Set the status of the additional app bar icons.  The default is `false`.
694         if (sharedPreferences.getBoolean("display_additional_app_bar_icons", false)) {
695             toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
696             toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
697             toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
698         } else { //Do not display the additional icons.
699             toggleFirstPartyCookies.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
700             toggleDomStorage.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
701             toggleSaveFormData.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
702         }
703
704         return true;
705     }
706
707     @Override
708     public boolean onPrepareOptionsMenu(Menu menu) {
709         // Get handles for the menu items.
710         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
711         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
712         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
713         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
714         MenuItem clearCookies = menu.findItem(R.id.clearCookies);
715         MenuItem clearFormData = menu.findItem(R.id.clearFormData);
716         MenuItem refreshMenuItem = menu.findItem(R.id.refresh);
717
718         // Set the status of the menu item checkboxes.
719         toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
720         toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
721         toggleDomStorage.setChecked(domStorageEnabled);
722         toggleSaveFormData.setChecked(saveFormDataEnabled);
723
724         // Enable third-party cookies if first-party cookies are enabled.
725         toggleThirdPartyCookies.setEnabled(firstPartyCookiesEnabled);
726
727         // Enable DOM Storage if JavaScript is enabled.
728         toggleDomStorage.setEnabled(javaScriptEnabled);
729
730         // Enable Clear Cookies if there are any.
731         clearCookies.setEnabled(cookieManager.hasCookies());
732
733         // Enable Clear Form Data is there is any.
734         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
735         clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
736
737         // Only show `Refresh` if `swipeToRefresh` is disabled.
738         refreshMenuItem.setVisible(!swipeToRefreshEnabled);
739
740         // Initialize font size variables.
741         int fontSize = mainWebView.getSettings().getTextZoom();
742         String fontSizeTitle;
743         MenuItem selectedFontSizeMenuItem;
744
745         // Prepare the font size title and current size menu item.
746         switch (fontSize) {
747             case 50:
748                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.fifty_percent);
749                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeFiftyPercent);
750                 break;
751
752             case 75:
753                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.seventy_five_percent);
754                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeSeventyFivePercent);
755                 break;
756
757             case 100:
758                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
759                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
760                 break;
761
762             case 125:
763                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_twenty_five_percent);
764                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredTwentyFivePercent);
765                 break;
766
767             case 150:
768                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_fifty_percent);
769                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredFiftyPercent);
770                 break;
771
772             case 175:
773                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_seventy_five_percent);
774                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredSeventyFivePercent);
775                 break;
776
777             case 200:
778                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.two_hundred_percent);
779                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeTwoHundredPercent);
780                 break;
781
782             default:
783                 fontSizeTitle = getResources().getString(R.string.font_size) + " - " + getResources().getString(R.string.one_hundred_percent);
784                 selectedFontSizeMenuItem = menu.findItem(R.id.fontSizeOneHundredPercent);
785                 break;
786         }
787
788         // Set the font size title and select the current size menu item.
789         MenuItem fontSizeMenuItem = menu.findItem(R.id.fontSize);
790         fontSizeMenuItem.setTitle(fontSizeTitle);
791         selectedFontSizeMenuItem.setChecked(true);
792
793         // Run all the other default commands.
794         super.onPrepareOptionsMenu(menu);
795
796         // `return true` displays the menu.
797         return true;
798     }
799
800     @Override
801     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
802     @SuppressLint("SetJavaScriptEnabled")
803     // removeAllCookies is deprecated, but it is required for API < 21.
804     @SuppressWarnings("deprecation")
805     public boolean onOptionsItemSelected(MenuItem menuItem) {
806         int menuItemId = menuItem.getItemId();
807
808         // Set the commands that relate to the menu entries.
809         switch (menuItemId) {
810             case R.id.toggleJavaScript:
811                 // Switch the status of javaScriptEnabled.
812                 javaScriptEnabled = !javaScriptEnabled;
813
814                 // Apply the new JavaScript status.
815                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
816
817                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
818                 updatePrivacyIcons(true);
819
820                 // Display a `Snackbar`.
821                 if (javaScriptEnabled) {  // JavaScrip is enabled.
822                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
823                 } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled, but first-party cookies are enabled.
824                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
825                 } else {  // Privacy mode.
826                     Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
827                 }
828
829                 // Reload the WebView.
830                 mainWebView.reload();
831                 return true;
832
833             case R.id.toggleFirstPartyCookies:
834                 // Switch the status of firstPartyCookiesEnabled.
835                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
836
837                 // Update the menu checkbox.
838                 menuItem.setChecked(firstPartyCookiesEnabled);
839
840                 // Apply the new cookie status.
841                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
842
843                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
844                 updatePrivacyIcons(true);
845
846                 // Display a `Snackbar`.
847                 if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
848                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
849                 } else if (javaScriptEnabled){  // JavaScript is still enabled.
850                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
851                 } else {  // Privacy mode.
852                     Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
853                 }
854
855                 // Reload the WebView.
856                 mainWebView.reload();
857                 return true;
858
859             case R.id.toggleThirdPartyCookies:
860                 if (Build.VERSION.SDK_INT >= 21) {
861                     // Switch the status of thirdPartyCookiesEnabled.
862                     thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
863
864                     // Update the menu checkbox.
865                     menuItem.setChecked(thirdPartyCookiesEnabled);
866
867                     // Apply the new cookie status.
868                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
869
870                     // Display a `Snackbar`.
871                     if (thirdPartyCookiesEnabled) {
872                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
873                     } else {
874                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
875                     }
876
877                     // Reload the WebView.
878                     mainWebView.reload();
879                 } // Else do nothing because SDK < 21.
880                 return true;
881
882             case R.id.toggleDomStorage:
883                 // Switch the status of domStorageEnabled.
884                 domStorageEnabled = !domStorageEnabled;
885
886                 // Update the menu checkbox.
887                 menuItem.setChecked(domStorageEnabled);
888
889                 // Apply the new DOM Storage status.
890                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
891
892                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
893                 updatePrivacyIcons(true);
894
895                 // Display a `Snackbar`.
896                 if (domStorageEnabled) {
897                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
898                 } else {
899                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
900                 }
901
902                 // Reload the WebView.
903                 mainWebView.reload();
904                 return true;
905
906             case R.id.toggleSaveFormData:
907                 // Switch the status of saveFormDataEnabled.
908                 saveFormDataEnabled = !saveFormDataEnabled;
909
910                 // Update the menu checkbox.
911                 menuItem.setChecked(saveFormDataEnabled);
912
913                 // Apply the new form data status.
914                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
915
916                 // Display a `Snackbar`.
917                 if (saveFormDataEnabled) {
918                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_enabled, Snackbar.LENGTH_SHORT).show();
919                 } else {
920                     Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_disabled, Snackbar.LENGTH_SHORT).show();
921                 }
922
923                 // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
924                 updatePrivacyIcons(true);
925
926                 // Reload the WebView.
927                 mainWebView.reload();
928                 return true;
929
930             case R.id.clearCookies:
931                 if (Build.VERSION.SDK_INT < 21) {
932                     cookieManager.removeAllCookie();
933                 } else {
934                     cookieManager.removeAllCookies(null);
935                 }
936                 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
937                 return true;
938
939             case R.id.clearDomStorage:
940                 WebStorage webStorage = WebStorage.getInstance();
941                 webStorage.deleteAllData();
942                 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
943                 return true;
944
945             case R.id.clearFormData:
946                 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
947                 mainWebViewDatabase.clearFormData();
948                 Snackbar.make(findViewById(R.id.mainWebView), R.string.form_data_deleted, Snackbar.LENGTH_SHORT).show();
949                 return true;
950
951             case R.id.fontSizeFiftyPercent:
952                 mainWebView.getSettings().setTextZoom(50);
953                 return true;
954
955             case R.id.fontSizeSeventyFivePercent:
956                 mainWebView.getSettings().setTextZoom(75);
957                 return true;
958
959             case R.id.fontSizeOneHundredPercent:
960                 mainWebView.getSettings().setTextZoom(100);
961                 return true;
962
963             case R.id.fontSizeOneHundredTwentyFivePercent:
964                 mainWebView.getSettings().setTextZoom(125);
965                 return true;
966
967             case R.id.fontSizeOneHundredFiftyPercent:
968                 mainWebView.getSettings().setTextZoom(150);
969                 return true;
970
971             case R.id.fontSizeOneHundredSeventyFivePercent:
972                 mainWebView.getSettings().setTextZoom(175);
973                 return true;
974
975             case R.id.fontSizeTwoHundredPercent:
976                 mainWebView.getSettings().setTextZoom(200);
977                 return true;
978
979             case R.id.find_on_page:
980                 // Hide the URL app bar.
981                 Toolbar appBarToolbar = (Toolbar) findViewById(R.id.app_bar);
982                 appBarToolbar.setVisibility(View.GONE);
983
984                 // Show the Find on Page `RelativeLayout`.
985                 LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
986                 findOnPageLinearLayout.setVisibility(View.VISIBLE);
987
988                 // Display the keyboard.  We have to wait 200 ms before running the command to work around a bug in Android.
989                 // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working
990                 findOnPageEditText.postDelayed(new Runnable()
991                 {
992                     @Override
993                     public void run()
994                     {
995                         // Set the focus on `findOnPageEditText`.
996                         findOnPageEditText.requestFocus();
997
998                         // Display the keyboard.
999                         inputMethodManager.showSoftInput(findOnPageEditText, 0);
1000                     }
1001                 }, 200);
1002                 return true;
1003
1004             case R.id.share:
1005                 Intent shareIntent = new Intent();
1006                 shareIntent.setAction(Intent.ACTION_SEND);
1007                 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
1008                 shareIntent.setType("text/plain");
1009                 startActivity(Intent.createChooser(shareIntent, "Share URL"));
1010                 return true;
1011
1012             case R.id.addToHomescreen:
1013                 // Show the `CreateHomeScreenShortcut` `AlertDialog` and name this instance `R.string.create_shortcut`.
1014                 AppCompatDialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
1015                 createHomeScreenShortcutDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.create_shortcut));
1016
1017                 //Everything else will be handled by `CreateHomeScreenShortcut` and the associated listener below.
1018                 return true;
1019
1020             case R.id.print:
1021                 // Get a `PrintManager` instance.
1022                 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
1023
1024                 // Convert `mainWebView` to `printDocumentAdapter`.
1025                 PrintDocumentAdapter printDocumentAdapter = mainWebView.createPrintDocumentAdapter();
1026
1027                 // Print the document.  The print attributes are `null`.
1028                 printManager.print(getResources().getString(R.string.privacy_browser_web_page), printDocumentAdapter, null);
1029                 return true;
1030
1031             case R.id.refresh:
1032                 mainWebView.reload();
1033                 return true;
1034
1035             default:
1036                 // Don't consume the event.
1037                 return super.onOptionsItemSelected(menuItem);
1038         }
1039     }
1040
1041     // removeAllCookies is deprecated, but it is required for API < 21.
1042     @SuppressWarnings("deprecation")
1043     @Override
1044     public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
1045         int menuItemId = menuItem.getItemId();
1046
1047         switch (menuItemId) {
1048             case R.id.home:
1049                 mainWebView.loadUrl(homepage, customHeaders);
1050                 break;
1051
1052             case R.id.back:
1053                 if (mainWebView.canGoBack()) {
1054                     mainWebView.goBack();
1055                 }
1056                 break;
1057
1058             case R.id.forward:
1059                 if (mainWebView.canGoForward()) {
1060                     mainWebView.goForward();
1061                 }
1062                 break;
1063
1064             case R.id.history:
1065                 // Gte the `WebBackForwardList`.
1066                 WebBackForwardList webBackForwardList = mainWebView.copyBackForwardList();
1067
1068                 // Show the `UrlHistory` `AlertDialog` and name this instance `R.string.history`.  `this` is the `Context`.
1069                 AppCompatDialogFragment urlHistoryDialogFragment = UrlHistory.loadBackForwardList(this, webBackForwardList);
1070                 urlHistoryDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.history));
1071                 break;
1072
1073             case R.id.bookmarks:
1074                 // Launch Bookmarks.
1075                 Intent bookmarksIntent = new Intent(this, Bookmarks.class);
1076                 startActivity(bookmarksIntent);
1077                 break;
1078
1079             case R.id.downloads:
1080                 // Launch the system Download Manager.
1081                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
1082
1083                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
1084                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1085
1086                 startActivity(downloadManagerIntent);
1087                 break;
1088
1089             case R.id.settings:
1090                 // Launch `Settings`.
1091                 Intent settingsIntent = new Intent(this, Settings.class);
1092                 startActivity(settingsIntent);
1093                 break;
1094
1095             case R.id.guide:
1096                 // Launch `Guide`.
1097                 Intent guideIntent = new Intent(this, Guide.class);
1098                 startActivity(guideIntent);
1099                 break;
1100
1101             case R.id.about:
1102                 // Launch `About`.
1103                 Intent aboutIntent = new Intent(this, About.class);
1104                 startActivity(aboutIntent);
1105                 break;
1106
1107             case R.id.clearAndExit:
1108                 // Clear cookies.  The commands changed slightly in API 21.
1109                 if (Build.VERSION.SDK_INT >= 21) {
1110                     cookieManager.removeAllCookies(null);
1111                 } else {
1112                     cookieManager.removeAllCookie();
1113                 }
1114
1115                 // Clear DOM storage.
1116                 WebStorage domStorage = WebStorage.getInstance();
1117                 domStorage.deleteAllData();
1118
1119                 // Clear form data.
1120                 WebViewDatabase webViewDatabase = WebViewDatabase.getInstance(this);
1121                 webViewDatabase.clearFormData();
1122
1123                 // Clear cache.  The argument of "true" includes disk files.
1124                 mainWebView.clearCache(true);
1125
1126                 // Clear the back/forward history.
1127                 mainWebView.clearHistory();
1128
1129                 // Clear any SSL certificate preferences.
1130                 mainWebView.clearSslPreferences();
1131
1132                 // Clear `formattedUrlString`.
1133                 formattedUrlString = null;
1134
1135                 // Clear `customHeaders`.
1136                 customHeaders.clear();
1137
1138                 // Detach all views from `mainWebViewRelativeLayout`.
1139                 RelativeLayout mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.mainWebViewRelativeLayout);
1140                 mainWebViewRelativeLayout.removeAllViews();
1141
1142                 // Destroy the internal state of `mainWebView`.
1143                 mainWebView.destroy();
1144
1145                 // Close Privacy Browser.  `finishAndRemoveTask` also removes Privacy Browser from the recent app list.
1146                 if (Build.VERSION.SDK_INT >= 21) {
1147                     finishAndRemoveTask();
1148                 } else {
1149                     finish();
1150                 }
1151
1152                 // Remove the terminated program from RAM.  The status code is `0`.
1153                 System.exit(0);
1154                 break;
1155
1156             default:
1157                 break;
1158         }
1159
1160         // Close the navigation drawer.
1161         drawerLayout.closeDrawer(GravityCompat.START);
1162         return true;
1163     }
1164
1165     @Override
1166     public void onPostCreate(Bundle savedInstanceState) {
1167         super.onPostCreate(savedInstanceState);
1168
1169         // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
1170         drawerToggle.syncState();
1171     }
1172
1173     @Override
1174     public void onConfigurationChanged(Configuration newConfig) {
1175         super.onConfigurationChanged(newConfig);
1176
1177         // Reload the ad for the free flavor if we are not in full screen mode.
1178         if (BuildConfig.FLAVOR.contentEquals("free") && adView.isShown() && !fullScreenVideoFrameLayout.isShown()) {
1179             // Reload the ad.
1180             BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
1181
1182             // Reinitialize the `adView` variable, as the `View` will have been removed and re-added by `BannerAd.reloadAfterRotate()`.
1183             adView = findViewById(R.id.adView);
1184         }
1185
1186         // `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
1187         // ActivityCompat.invalidateOptionsMenu(this);
1188     }
1189
1190     @Override
1191     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
1192         // Store the `HitTestResult`.
1193         final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult();
1194
1195         // Create strings.
1196         final String imageUrl;
1197         final String linkUrl;
1198
1199         // Get a handle for the `ClipboardManager`.
1200         final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
1201
1202         switch (hitTestResult.getType()) {
1203             // `SRC_ANCHOR_TYPE` is a link.
1204             case WebView.HitTestResult.SRC_ANCHOR_TYPE:
1205                 // Get the target URL.
1206                 linkUrl = hitTestResult.getExtra();
1207
1208                 // Set the target URL as the title of the `ContextMenu`.
1209                 menu.setHeaderTitle(linkUrl);
1210
1211                 // Add a `Load URL` entry.
1212                 menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1213                     @Override
1214                     public boolean onMenuItemClick(MenuItem item) {
1215                         mainWebView.loadUrl(linkUrl, customHeaders);
1216                         return false;
1217                     }
1218                 });
1219
1220                 // Add a `Copy URL` entry.
1221                 menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1222                     @Override
1223                     public boolean onMenuItemClick(MenuItem item) {
1224                         // Save the link URL in a `ClipData`.
1225                         ClipData srcAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), linkUrl);
1226
1227                         // Set the `ClipData` as the clipboard's primary clip.
1228                         clipboardManager.setPrimaryClip(srcAnchorTypeClipData);
1229                         return false;
1230                     }
1231                 });
1232
1233                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1234                 menu.add(R.string.cancel);
1235                 break;
1236
1237             case WebView.HitTestResult.EMAIL_TYPE:
1238                 // Get the target URL.
1239                 linkUrl = hitTestResult.getExtra();
1240
1241                 // Set the target URL as the title of the `ContextMenu`.
1242                 menu.setHeaderTitle(linkUrl);
1243
1244                 // Add a `Write Email` entry.
1245                 menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1246                     @Override
1247                     public boolean onMenuItemClick(MenuItem item) {
1248                         // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
1249                         Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
1250
1251                         // Parse the url and set it as the data for the `Intent`.
1252                         emailIntent.setData(Uri.parse("mailto:" + linkUrl));
1253
1254                         // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
1255                         emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1256
1257                         // Make it so.
1258                         startActivity(emailIntent);
1259                         return false;
1260                     }
1261                 });
1262
1263                 // Add a `Copy Email Address` entry.
1264                 menu.add(R.string.copy_email_address).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1265                     @Override
1266                     public boolean onMenuItemClick(MenuItem item) {
1267                         // Save the email address in a `ClipData`.
1268                         ClipData srcEmailTypeClipData = ClipData.newPlainText(getResources().getString(R.string.email_address), linkUrl);
1269
1270                         // Set the `ClipData` as the clipboard's primary clip.
1271                         clipboardManager.setPrimaryClip(srcEmailTypeClipData);
1272                         return false;
1273                     }
1274                 });
1275
1276                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1277                 menu.add(R.string.cancel);
1278                 break;
1279
1280             // `IMAGE_TYPE` is an image.
1281             case WebView.HitTestResult.IMAGE_TYPE:
1282                 // Get the image URL.
1283                 imageUrl = hitTestResult.getExtra();
1284
1285                 // Set the image URL as the title of the `ContextMenu`.
1286                 menu.setHeaderTitle(imageUrl);
1287
1288                 // Add a `View Image` entry.
1289                 menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1290                     @Override
1291                     public boolean onMenuItemClick(MenuItem item) {
1292                         mainWebView.loadUrl(imageUrl, customHeaders);
1293                         return false;
1294                     }
1295                 });
1296
1297                 // Add a `Download Image` entry.
1298                 menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1299                     @Override
1300                     public boolean onMenuItemClick(MenuItem item) {
1301                         // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`.
1302                         AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl);
1303                         downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
1304                         return false;
1305                     }
1306                 });
1307
1308                 // Add a `Copy URL` entry.
1309                 menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1310                     @Override
1311                     public boolean onMenuItemClick(MenuItem item) {
1312                         // Save the image URL in a `ClipData`.
1313                         ClipData srcImageTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
1314
1315                         // Set the `ClipData` as the clipboard's primary clip.
1316                         clipboardManager.setPrimaryClip(srcImageTypeClipData);
1317                         return false;
1318                     }
1319                 });
1320
1321                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1322                 menu.add(R.string.cancel);
1323                 break;
1324
1325
1326             // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link.
1327             case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE:
1328                 // Get the image URL.
1329                 imageUrl = hitTestResult.getExtra();
1330
1331                 // Set the image URL as the title of the `ContextMenu`.
1332                 menu.setHeaderTitle(imageUrl);
1333
1334                 // Add a `View Image` entry.
1335                 menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1336                     @Override
1337                     public boolean onMenuItemClick(MenuItem item) {
1338                         mainWebView.loadUrl(imageUrl, customHeaders);
1339                         return false;
1340                     }
1341                 });
1342
1343                 // Add a `Download Image` entry.
1344                 menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1345                     @Override
1346                     public boolean onMenuItemClick(MenuItem item) {
1347                         // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`.
1348                         AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl);
1349                         downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download));
1350                         return false;
1351                     }
1352                 });
1353
1354                 // Add a `Copy URL` entry.
1355                 menu.add(R.string.copy_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
1356                     @Override
1357                     public boolean onMenuItemClick(MenuItem item) {
1358                         // Save the image URL in a `ClipData`.
1359                         ClipData srcImageAnchorTypeClipData = ClipData.newPlainText(getResources().getString(R.string.url), imageUrl);
1360
1361                         // Set the `ClipData` as the clipboard's primary clip.
1362                         clipboardManager.setPrimaryClip(srcImageAnchorTypeClipData);
1363                         return false;
1364                     }
1365                 });
1366
1367                 // Add a `Cancel` entry, which by default closes the `ContextMenu`.
1368                 menu.add(R.string.cancel);
1369                 break;
1370         }
1371     }
1372
1373     @Override
1374     public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) {
1375         // Get shortcutNameEditText from the alert dialog.
1376         EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
1377
1378         // Create the bookmark shortcut based on formattedUrlString.
1379         Intent bookmarkShortcut = new Intent();
1380         bookmarkShortcut.setAction(Intent.ACTION_VIEW);
1381         bookmarkShortcut.setData(Uri.parse(formattedUrlString));
1382
1383         // Place the bookmark shortcut on the home screen.
1384         Intent placeBookmarkShortcut = new Intent();
1385         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
1386         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
1387         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
1388         placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
1389         sendBroadcast(placeBookmarkShortcut);
1390     }
1391
1392     @Override
1393     public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) {
1394         // Get a handle for the system `DOWNLOAD_SERVICE`.
1395         DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1396
1397         // Parse `imageUrl`.
1398         DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl));
1399
1400         // Get the file name from `dialogFragment`.
1401         EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name);
1402         String imageName = downloadImageNameEditText.getText().toString();
1403
1404         // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1405         if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`.
1406             downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName);
1407         } else { // Only set the title using `imageName`.
1408             downloadRequest.setTitle(imageName);
1409         }
1410
1411         // Allow `MediaScanner` to index the download if it is a media file.
1412         downloadRequest.allowScanningByMediaScanner();
1413
1414         // Add the URL as the description for the download.
1415         downloadRequest.setDescription(imageUrl);
1416
1417         // Show the download notification after the download is completed.
1418         downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1419
1420         // Initiate the download.
1421         downloadManager.enqueue(downloadRequest);
1422     }
1423
1424     @Override
1425     public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) {
1426         // Get a handle for the system `DOWNLOAD_SERVICE`.
1427         DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
1428
1429         // Parse `downloadUrl`.
1430         DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl));
1431
1432         // Get the file name from `dialogFragment`.
1433         EditText downloadFileNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_file_name);
1434         String fileName = downloadFileNameEditText.getText().toString();
1435
1436         // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`.
1437         if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `fileName`.
1438             downloadRequest.setDestinationInExternalFilesDir(this, "/", fileName);
1439         } else { // Only set the title using `fileName`.
1440             downloadRequest.setTitle(fileName);
1441         }
1442
1443         // Allow `MediaScanner` to index the download if it is a media file.
1444         downloadRequest.allowScanningByMediaScanner();
1445
1446         // Add the URL as the description for the download.
1447         downloadRequest.setDescription(downloadUrl);
1448
1449         // Show the download notification after the download is completed.
1450         downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
1451
1452         // Initiate the download.
1453         downloadManager.enqueue(downloadRequest);
1454     }
1455
1456     public void viewSslCertificate(View view) {
1457         // Show the `ViewSslCertificate` `AlertDialog` and name this instance `@string/view_ssl_certificate`.
1458         DialogFragment viewSslCertificateDialogFragment = new ViewSslCertificate();
1459         viewSslCertificateDialogFragment.show(getFragmentManager(), getResources().getString(R.string.view_ssl_certificate));
1460     }
1461
1462     @Override
1463     public void onSslErrorCancel() {
1464         sslErrorHandler.cancel();
1465     }
1466
1467     @Override
1468     public void onSslErrorProceed() {
1469         sslErrorHandler.proceed();
1470     }
1471
1472     @Override
1473     public void onUrlHistoryEntrySelected(int moveBackOrForwardSteps) {
1474         // Load the history entry.
1475         mainWebView.goBackOrForward(moveBackOrForwardSteps);
1476     }
1477
1478     @Override
1479     public void onClearHistory() {
1480         // Clear the history.
1481         mainWebView.clearHistory();
1482     }
1483
1484     // Override `onBackPressed` to handle the navigation drawer and `mainWebView`.
1485     @Override
1486     public void onBackPressed() {
1487         // Close the navigation drawer if it is available.  GravityCompat.START is the drawer on the left on Left-to-Right layout text.
1488         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
1489             drawerLayout.closeDrawer(GravityCompat.START);
1490         } else {
1491             // Load the previous URL if available.
1492             if (mainWebView.canGoBack()) {
1493                 mainWebView.goBack();
1494             } else {
1495                 // Pass `onBackPressed()` to the system.
1496                 super.onBackPressed();
1497             }
1498         }
1499     }
1500
1501     @Override
1502     public void onPause() {
1503         // Pause `mainWebView`.
1504         mainWebView.onPause();
1505
1506         // Stop all JavaScript.
1507         mainWebView.pauseTimers();
1508
1509         // Pause the adView or it will continue to consume resources in the background on the free flavor.
1510         if (BuildConfig.FLAVOR.contentEquals("free")) {
1511             BannerAd.pauseAd(adView);
1512         }
1513
1514         super.onPause();
1515     }
1516
1517     @Override
1518     public void onResume() {
1519         super.onResume();
1520
1521         // Resume JavaScript (if enabled).
1522         mainWebView.resumeTimers();
1523
1524         // Resume `mainWebView`.
1525         mainWebView.onResume();
1526
1527         // Resume the adView for the free flavor.
1528         if (BuildConfig.FLAVOR.contentEquals("free")) {
1529             BannerAd.resumeAd(adView);
1530         }
1531     }
1532
1533     @Override
1534     public void onRestart() {
1535         super.onRestart();
1536
1537         // Apply the settings from shared preferences, which might have been changed in `Settings`.
1538         applySettings();
1539
1540         // Update the privacy icon.  `true` runs `invalidateOptionsMenu` as the last step.
1541         updatePrivacyIcons(true);
1542
1543     }
1544
1545     private void loadUrlFromTextBox() throws UnsupportedEncodingException {
1546         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
1547         String unformattedUrlString = urlTextBox.getText().toString().trim();
1548
1549         URL unformattedUrl = null;
1550         Uri.Builder formattedUri = new Uri.Builder();
1551
1552         // Check to see if unformattedUrlString is a valid URL.  Otherwise, convert it into a Duck Duck Go search.
1553         if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
1554             // Add http:// at the beginning if it is missing.  Otherwise the app will segfault.
1555             if (!unformattedUrlString.startsWith("http")) {
1556                 unformattedUrlString = "http://" + unformattedUrlString;
1557             }
1558
1559             // 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.
1560             try {
1561                 unformattedUrl = new URL(unformattedUrlString);
1562             } catch (MalformedURLException e) {
1563                 e.printStackTrace();
1564             }
1565
1566             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
1567             final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
1568             final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
1569             final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
1570             final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
1571             final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
1572
1573             formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
1574             formattedUrlString = formattedUri.build().toString();
1575         } else {
1576             // Sanitize the search input and convert it to a DuckDuckGo search.
1577             final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
1578
1579             // Use the correct search URL.
1580             if (javaScriptEnabled) {  // JavaScript is enabled.
1581                 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
1582             } else { // JavaScript is disabled.
1583                 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
1584             }
1585         }
1586
1587         mainWebView.loadUrl(formattedUrlString, customHeaders);
1588
1589         // Hide the keyboard so we can see the webpage.  `0` indicates no additional flags.
1590         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1591     }
1592
1593     public void findPreviousOnPage(View view) {
1594         // Go to the previous highlighted phrase on the page.  `false` goes backwards instead of forwards.
1595         mainWebView.findNext(false);
1596     }
1597
1598     public void findNextOnPage(View view) {
1599         // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards.
1600         mainWebView.findNext(true);
1601     }
1602
1603     public void closeFindOnPage(View view) {
1604         // Delete the contents of `find_on_page_edittext`.
1605         findOnPageEditText.setText(null);
1606
1607         // Clear the highlighted phrases.
1608         mainWebView.clearMatches();
1609
1610         // Hide the Find on Page `RelativeLayout`.
1611         LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout);
1612         findOnPageLinearLayout.setVisibility(View.GONE);
1613
1614         // Show the URL app bar.
1615         Toolbar appBarToolbar = (Toolbar) findViewById(R.id.app_bar);
1616         appBarToolbar.setVisibility(View.VISIBLE);
1617
1618         // Hide the keyboard so we can see the webpage.  `0` indicates no additional flags.
1619         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
1620     }
1621
1622     private void applySettings() {
1623         // Get the shared preference values.  `this` references the current context.
1624         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
1625
1626         // Store the values from `sharedPreferences` in variables.
1627         String userAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
1628         String customUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
1629         String javaScriptDisabledSearchString = sharedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
1630         String javaScriptDisabledCustomSearchString = sharedPreferences.getString("javascript_disabled_search_custom_url", "");
1631         String javaScriptEnabledSearchString = sharedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
1632         String javaScriptEnabledCustomSearchString = sharedPreferences.getString("javascript_enabled_search_custom_url", "");
1633         String homepageString = sharedPreferences.getString("homepage", "https://www.duckduckgo.com");
1634         String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
1635         swipeToRefreshEnabled = sharedPreferences.getBoolean("swipe_to_refresh_enabled", false);
1636         boolean doNotTrackEnabled = sharedPreferences.getBoolean("do_not_track", true);
1637         boolean proxyThroughOrbot = sharedPreferences.getBoolean("proxy_through_orbot", false);
1638         fullScreenBrowsingModeEnabled = sharedPreferences.getBoolean("enable_full_screen_browsing_mode", false);
1639         hideSystemBarsOnFullscreen = sharedPreferences.getBoolean("hide_system_bars", false);
1640         translucentNavigationBarOnFullscreen = sharedPreferences.getBoolean("translucent_navigation_bar", true);
1641
1642         // Because they can be modified on-the-fly by the user, these default settings are only applied when the program first runs.
1643         if (javaScriptEnabled == null) {  // If `javaScriptEnabled` is null the program is just starting.
1644             // Get the values from `sharedPreferences`.
1645             javaScriptEnabled = sharedPreferences.getBoolean("javascript_enabled", false);
1646             firstPartyCookiesEnabled = sharedPreferences.getBoolean("first_party_cookies_enabled", false);
1647             thirdPartyCookiesEnabled = sharedPreferences.getBoolean("third_party_cookies_enabled", false);
1648             domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
1649             saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
1650
1651             // Apply the default settings.
1652             mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
1653             cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
1654             mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
1655             mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
1656             mainWebView.getSettings().setTextZoom(Integer.valueOf(defaultFontSizeString));
1657
1658             // Set third-party cookies status if API >= 21.
1659             if (Build.VERSION.SDK_INT >= 21) {
1660                 cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
1661             }
1662         }
1663
1664         // Apply the other settings from `sharedPreferences`.
1665         homepage = homepageString;
1666         swipeRefreshLayout.setEnabled(swipeToRefreshEnabled);
1667
1668         // Set the user agent initial status.
1669         switch (userAgentString) {
1670             case "Default user agent":
1671                 // Set the user agent to `""`, which uses the default value.
1672                 mainWebView.getSettings().setUserAgentString("");
1673                 break;
1674
1675             case "Custom user agent":
1676                 // Set the custom user agent.
1677                 mainWebView.getSettings().setUserAgentString(customUserAgentString);
1678                 break;
1679
1680             default:
1681                 // Use the selected user agent.
1682                 mainWebView.getSettings().setUserAgentString(userAgentString);
1683                 break;
1684         }
1685
1686         // Set JavaScript disabled search.
1687         if (javaScriptDisabledSearchString.equals("Custom URL")) {  // Get the custom URL string.
1688             javaScriptDisabledSearchURL = javaScriptDisabledCustomSearchString;
1689         } else {  // Use the string from the pre-built list.
1690             javaScriptDisabledSearchURL = javaScriptDisabledSearchString;
1691         }
1692
1693         // Set JavaScript enabled search.
1694         if (javaScriptEnabledSearchString.equals("Custom URL")) {  // Get the custom URL string.
1695             javaScriptEnabledSearchURL = javaScriptEnabledCustomSearchString;
1696         } else {  // Use the string from the pre-built list.
1697             javaScriptEnabledSearchURL = javaScriptEnabledSearchString;
1698         }
1699
1700         // Set Do Not Track status.
1701         if (doNotTrackEnabled) {
1702             customHeaders.put("DNT", "1");
1703         } else {
1704             customHeaders.remove("DNT");
1705         }
1706
1707         // Set Orbot proxy status.
1708         if (proxyThroughOrbot) {
1709             // Set the proxy.  `this` refers to the current activity where an `AlertDialog` might be displayed.
1710             OrbotProxyHelper.setProxy(getApplicationContext(), this, "localhost", "8118");
1711         } else {  // Reset the proxy to default.  The host is `""` and the port is `"0"`.
1712             OrbotProxyHelper.setProxy(getApplicationContext(), this, "", "0");
1713         }
1714
1715         // If we are in full screen mode update the `SYSTEM_UI` flags.
1716         if (!appBar.isShowing()) {
1717             if (hideSystemBarsOnFullscreen) {  // Hide everything.
1718                 // Remove the translucent navigation setting if it is currently flagged.
1719                 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1720
1721                 // Remove the translucent status bar overlay.
1722                 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1723
1724                 // Remove the translucent status bar overlay on the `Drawer Layout`, which is special and needs its own command.
1725                 drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1726
1727                 /* SYSTEM_UI_FLAG_FULLSCREEN hides the status bar at the top of the screen.
1728                  * SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bar on the bottom or right of the screen.
1729                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the status and navigation bars translucent and automatically rehides them after they are shown.
1730                  */
1731                 rootCoordinatorLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
1732             } else {  // Hide everything except the status and navigation bars.
1733                 // Add the translucent status flag if it is unset.
1734                 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
1735
1736                 if (translucentNavigationBarOnFullscreen) {
1737                     // Set the navigation bar to be translucent.
1738                     getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1739                 } else {
1740                     // Set the navigation bar to be black.
1741                     getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
1742                 }
1743             }
1744         }
1745     }
1746
1747     private void updatePrivacyIcons(boolean runInvalidateOptionsMenu) {
1748         // Get handles for the icons.
1749         MenuItem privacyIcon = mainMenu.findItem(R.id.toggleJavaScript);
1750         MenuItem firstPartyCookiesIcon = mainMenu.findItem(R.id.toggleFirstPartyCookies);
1751         MenuItem domStorageIcon = mainMenu.findItem(R.id.toggleDomStorage);
1752         MenuItem formDataIcon = mainMenu.findItem(R.id.toggleSaveFormData);
1753
1754         // Update `privacyIcon`.
1755         if (javaScriptEnabled) {  // JavaScript is enabled.
1756             privacyIcon.setIcon(R.drawable.javascript_enabled);
1757         } else if (firstPartyCookiesEnabled) {  // JavaScript is disabled but cookies are enabled.
1758             privacyIcon.setIcon(R.drawable.warning);
1759         } else {  // All the dangerous features are disabled.
1760             privacyIcon.setIcon(R.drawable.privacy_mode);
1761         }
1762
1763         // Update `firstPartyCookiesIcon`.
1764         if (firstPartyCookiesEnabled) {  // First-party cookies are enabled.
1765             firstPartyCookiesIcon.setIcon(R.drawable.cookies_enabled);
1766         } else {  // First-party cookies are disabled.
1767             firstPartyCookiesIcon.setIcon(R.drawable.cookies_disabled);
1768         }
1769
1770         // Update `domStorageIcon`.
1771         if (javaScriptEnabled && domStorageEnabled) {  // Both JavaScript and DOM storage are enabled.
1772             domStorageIcon.setIcon(R.drawable.dom_storage_enabled);
1773         } else if (javaScriptEnabled) {  // JavaScript is enabled but DOM storage is disabled.
1774             domStorageIcon.setIcon(R.drawable.dom_storage_disabled);
1775         } else {  // JavaScript is disabled, so DOM storage is ghosted.
1776             domStorageIcon.setIcon(R.drawable.dom_storage_ghosted);
1777         }
1778
1779         // Update `formDataIcon`.
1780         if (saveFormDataEnabled) {  // Form data is enabled.
1781             formDataIcon.setIcon(R.drawable.form_data_enabled);
1782         } else {  // Form data is disabled.
1783             formDataIcon.setIcon(R.drawable.form_data_disabled);
1784         }
1785
1786         // `invalidateOptionsMenu` calls `onPrepareOptionsMenu()` and redraws the icons in the `AppBar`.  `this` references the current activity.
1787         if (runInvalidateOptionsMenu) {
1788             ActivityCompat.invalidateOptionsMenu(this);
1789         }
1790     }
1791 }