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