94ff112948c76b3949af7f70ffd15292ad44ab8b
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / MainWebViewActivity.java
1 /**
2  * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser;
21
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.DialogFragment;
25 import android.app.DownloadManager;
26 import android.content.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.MailTo;
33 import android.net.Uri;
34 import android.os.Build;
35 import android.os.Bundle;
36 import android.preference.PreferenceManager;
37 import android.support.design.widget.NavigationView;
38 import android.support.design.widget.Snackbar;
39 import android.support.v4.content.ContextCompat;
40 import android.support.v4.view.GravityCompat;
41 import android.support.v4.widget.DrawerLayout;
42 import android.support.v4.widget.SwipeRefreshLayout;
43 import android.support.v7.app.ActionBar;
44 import android.support.v7.app.ActionBarDrawerToggle;
45 import android.support.v7.app.AppCompatActivity;
46 import android.support.v7.widget.Toolbar;
47 import android.util.Patterns;
48 import android.view.KeyEvent;
49 import android.view.Menu;
50 import android.view.MenuItem;
51 import android.view.View;
52 import android.view.inputmethod.InputMethodManager;
53 import android.webkit.CookieManager;
54 import android.webkit.DownloadListener;
55 import android.webkit.WebChromeClient;
56 import android.webkit.WebStorage;
57 import android.webkit.WebView;
58 import android.webkit.WebViewClient;
59 import android.webkit.WebViewDatabase;
60 import android.widget.EditText;
61 import android.widget.FrameLayout;
62 import android.widget.ImageView;
63 import android.widget.ProgressBar;
64
65 import java.io.UnsupportedEncodingException;
66 import java.net.MalformedURLException;
67 import java.net.URL;
68 import java.net.URLEncoder;
69
70 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
71 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener {
72     // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`.
73     // It is also used in `onCreate()` and `onCreateHomeScreenShortcutCreate()`.
74     public static Bitmap favoriteIcon;
75
76     // mainWebView is public static so it can be accessed from SettingsFragment.
77     // It is also used in onCreate(), onOptionsItemSelected(), onNavigationItemSelected(), and loadUrlFromTextBox().
78     public static WebView mainWebView;
79
80     // formattedUrlString is public static so it can be accessed from BookmarksActivity.
81     // It is also used in onCreate(), onOptionsItemSelected(), onCreateHomeScreenShortcutCreate(), and loadUrlFromTextBox().
82     public static String formattedUrlString;
83
84     // mainMenu is public static so it can be accessed from SettingsFragment.  It is also used in onCreateOptionsMenu() and onOptionsItemSelected().
85     public static Menu mainMenu;
86
87     // cookieManager is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onOptionsItemSelected(), and onNavigationItemSelected().
88     public static CookieManager cookieManager;
89
90     // javaScriptEnabled is public static so it can be accessed from SettingsFragment.
91     // It is also used in onCreate(), onCreateOptionsMenu(), onOptionsItemSelected(), and loadUrlFromTextBox().
92     public static boolean javaScriptEnabled;
93
94     // firstPartyCookiesEnabled is public static so it can be accessed from SettingsFragment.
95     // It is also used in onCreate(), onCreateOptionsMenu(), onPrepareOptionsMenu(), and onOptionsItemSelected().
96     public static boolean firstPartyCookiesEnabled;
97
98     // thirdPartyCookiesEnabled is used in onCreate(), onCreateOptionsMenu(), onPrepareOptionsMenu(), and onOptionsItemSelected().
99     public static boolean thirdPartyCookiesEnabled;
100
101     // domStorageEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), and onOptionsItemSelected().
102     public static boolean domStorageEnabled;
103
104     // saveFormDataEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), and onOptionsItemSelected().
105     public static boolean saveFormDataEnabled;
106
107     // javaScriptDisabledSearchURL is public static so it can be accessed from SettingsFragment.  It is also used in onCreate() and loadURLFromTextBox().
108     public static String javaScriptDisabledSearchURL;
109
110     // javaScriptEnabledSearchURL is public static so it can be accessed from SettingsFragment.  It is also used in onCreate() and loadURLFromTextBox().
111     public static String javaScriptEnabledSearchURL;
112
113     // homepage is public static so it can be accessed from  SettingsFragment.  It is also used in onCreate() and onOptionsItemSelected().
114     public static String homepage;
115
116     // swipeToRefresh is public static so it can be accessed from SettingsFragment.  It is also used in onCreate().
117     public static SwipeRefreshLayout swipeToRefresh;
118
119     // swipeToRefreshEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate().
120     public static boolean swipeToRefreshEnabled;
121
122
123
124     // drawerToggle is used in onCreate(), onPostCreate(), onConfigurationChanged(), onNewIntent(), and onNavigationItemSelected().
125     private ActionBarDrawerToggle drawerToggle;
126
127     // drawerLayout is used in onCreate(), onNewIntent(), and onBackPressed().
128     private DrawerLayout drawerLayout;
129
130     // privacyIcon is used in onCreateOptionsMenu() and updatePrivacyIcon().
131     private MenuItem privacyIcon;
132
133     // urlTextBox is used in onCreate(), onOptionsItemSelected(), and loadUrlFromTextBox().
134     private EditText urlTextBox;
135
136     // adView is used in onCreate() and onConfigurationChanged().
137     private View adView;
138
139     @Override
140     // 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.
141     @SuppressLint("SetJavaScriptEnabled")
142     protected void onCreate(Bundle savedInstanceState) {
143         super.onCreate(savedInstanceState);
144         setContentView(R.layout.main_coordinatorlayout);
145
146         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
147         Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
148         setSupportActionBar(supportAppBar);
149         final ActionBar appBar = getSupportActionBar();
150
151         // This is needed to get rid of the Android Studio warning that appBar might be null.
152         assert appBar != null;
153
154         // Add the custom url_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
155         appBar.setCustomView(R.layout.url_bar);
156         appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
157
158         // Set the "go" button on the keyboard to load the URL in urlTextBox.
159         urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
160         urlTextBox.setOnKeyListener(new View.OnKeyListener() {
161             public boolean onKey(View v, int keyCode, KeyEvent event) {
162                 // If the event is a key-down event on the "enter" button, load the URL.
163                 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
164                     // Load the URL into the mainWebView and consume the event.
165                     try {
166                         loadUrlFromTextBox();
167                     } catch (UnsupportedEncodingException e) {
168                         e.printStackTrace();
169                     }
170                     // If the enter key was pressed, consume the event.
171                     return true;
172                 } else {
173                     // If any other key was pressed, do not consume the event.
174                     return false;
175                 }
176             }
177         });
178
179         final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
180
181         // Implement swipe to refresh
182         swipeToRefresh = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
183         assert swipeToRefresh != null; //This assert removes the incorrect warning on the following line that swipeToRefresh might be null.
184         swipeToRefresh.setColorSchemeResources(R.color.blue);
185         swipeToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
186             @Override
187             public void onRefresh() {
188                 mainWebView.reload();
189             }
190         });
191
192         mainWebView = (WebView) findViewById(R.id.mainWebView);
193
194         // Create the navigation drawer.
195         drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
196         // The DrawerTitle identifies the drawer in accessibility mode.
197         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
198
199         // Listen for touches on the navigation menu.
200         final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
201         assert navigationView != null; // This assert removes the incorrect warning on the following line that navigationView might be null.
202         navigationView.setNavigationItemSelectedListener(this);
203
204         // drawerToggle creates the hamburger icon at the start of the AppBar.
205         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
206
207         mainWebView.setWebViewClient(new WebViewClient() {
208             // shouldOverrideUrlLoading makes this `WebView` the default handler for URLs inside the app, so that links are not kicked out to other apps.
209             @Override
210             public boolean shouldOverrideUrlLoading(WebView view, String url) {
211                 // Use an external email program if the link begins with "mailto:".
212                 if (url.startsWith("mailto:")) {
213                     // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched.
214                     Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
215
216                     // Parse the url and set it as the data for the `Intent`.
217                     emailIntent.setData(Uri.parse(url));
218
219                     // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser.
220                     emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
221
222                     // Make it so.
223                     startActivity(emailIntent);
224                     return true;
225                 } else {  // Load the URL in Privacy Browser.
226                     mainWebView.loadUrl(url);
227                     return true;
228                 }
229             }
230
231             // Update the URL in urlTextBox when the page starts to load.
232             @Override
233             public void onPageStarted(WebView view, String url, Bitmap favicon) {
234                 urlTextBox.setText(url);
235             }
236
237             // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
238             @Override
239             public void onPageFinished(WebView view, String url) {
240                 formattedUrlString = url;
241
242                 // Only update urlTextBox if the user is not typing in it.
243                 if (!urlTextBox.hasFocus()) {
244                     urlTextBox.setText(formattedUrlString);
245                 }
246             }
247         });
248
249         mainWebView.setWebChromeClient(new WebChromeClient() {
250             // Update the progress bar when a page is loading.
251             @Override
252             public void onProgressChanged(WebView view, int progress) {
253                 ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
254                 progressBar.setProgress(progress);
255                 if (progress < 100) {
256                     progressBar.setVisibility(View.VISIBLE);
257                 } else {
258                     progressBar.setVisibility(View.GONE);
259
260                     //Stop the SwipeToRefresh indicator if it is running
261                     swipeToRefresh.setRefreshing(false);
262                 }
263             }
264
265             // Set the favorite icon when it changes.
266             @Override
267             public void onReceivedIcon(WebView view, Bitmap icon) {
268                 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
269                 favoriteIcon = icon;
270
271                 // Place the favorite icon in the appBar.
272                 ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
273                 imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
274             }
275
276             // Enter full screen video
277             @Override
278             public void onShowCustomView(View view, CustomViewCallback callback) {
279                 appBar.hide();
280
281                 // Show the fullScreenVideoFrameLayout.
282                 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
283                 fullScreenVideoFrameLayout.addView(view);
284                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
285
286                 // Hide the mainWebView.
287                 mainWebView.setVisibility(View.GONE);
288
289                 // Hide the ad if this is the free flavor.
290                 BannerAd.hideAd(adView);
291
292                 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
293                  * SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
294                  * SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
295                  */
296                 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
297             }
298
299             // Exit full screen video
300             public void onHideCustomView() {
301                 appBar.show();
302
303                 // Show the mainWebView.
304                 mainWebView.setVisibility(View.VISIBLE);
305
306                 // Show the ad if this is the free flavor.
307                 BannerAd.showAd(adView);
308
309                 // Hide the fullScreenVideoFrameLayout.
310                 assert fullScreenVideoFrameLayout != null; //This assert removes the incorrect warning on the following line that fullScreenVideoFrameLayout might be null.
311                 fullScreenVideoFrameLayout.removeAllViews();
312                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
313             }
314         });
315
316         // Allow the downloading of files.
317         mainWebView.setDownloadListener(new DownloadListener() {
318             // Launch the Android download manager when a link leads to a download.
319             @Override
320             public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
321                 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
322                 DownloadManager.Request requestUri = new DownloadManager.Request(Uri.parse(url));
323
324                 // Add the URL as the description for the download.
325                 requestUri.setDescription(url);
326
327                 // Show the download notification after the download is completed.
328                 requestUri.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
329
330                 // Initiate the download and display a Snackbar.
331                 downloadManager.enqueue(requestUri);
332                 Snackbar.make(findViewById(R.id.mainWebView), R.string.download_started, Snackbar.LENGTH_SHORT).show();
333             }
334         });
335
336         // Allow pinch to zoom.
337         mainWebView.getSettings().setBuiltInZoomControls(true);
338
339         // Hide zoom controls.
340         mainWebView.getSettings().setDisplayZoomControls(false);
341
342
343         // Initialize the default preference values the first time the program is run.
344         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
345
346         // Get the shared preference values.
347         SharedPreferences savedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
348
349         // Set JavaScript initial status.  The default value is false.
350         javaScriptEnabled = savedPreferences.getBoolean("javascript_enabled", false);
351         mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
352
353         // Initialize cookieManager.
354         cookieManager = CookieManager.getInstance();
355
356         // Set cookies initial status.  The default value is false.
357         firstPartyCookiesEnabled = savedPreferences.getBoolean("first_party_cookies_enabled", false);
358         cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
359
360         // Set third-party cookies initial status if API >= 21.  The default value is false.
361         if (Build.VERSION.SDK_INT >= 21) {
362             thirdPartyCookiesEnabled = savedPreferences.getBoolean("third_party_cookies_enabled", false);
363             cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
364         }
365
366         // Set DOM storage initial status.  The default value is false.
367         domStorageEnabled = savedPreferences.getBoolean("dom_storage_enabled", false);
368         mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
369
370         // Set the saved form data initial status.  The default is false.
371         saveFormDataEnabled = savedPreferences.getBoolean("save_form_data_enabled", false);
372         mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
373
374         // Set the user agent initial status.
375         String userAgentString = savedPreferences.getString("user_agent", "Default user agent");
376         switch (userAgentString) {
377             case "Default user agent":
378                 // Do nothing.
379                 break;
380
381             case "Custom user agent":
382                 // Set the custom user agent on mainWebView,  The default is "PrivacyBrowser/1.0".
383                 mainWebView.getSettings().setUserAgentString(savedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0"));
384                 break;
385
386             default:
387                 // Set the selected user agent on mainWebView.  The default is "PrivacyBrowser/1.0".
388                 mainWebView.getSettings().setUserAgentString(savedPreferences.getString("user_agent", "PrivacyBrowser/1.0"));
389                 break;
390         }
391
392         // Set the initial string for JavaScript disabled search.
393         if (savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=").equals("Custom URL")) {
394             // Get the custom URL string.  The default is "".
395             javaScriptDisabledSearchURL = savedPreferences.getString("javascript_disabled_search_custom_url", "");
396         } else {
397             // Use the string from javascript_disabled_search.
398             javaScriptDisabledSearchURL = savedPreferences.getString("javascript_disabled_search", "https://duckduckgo.com/html/?q=");
399         }
400
401         // Set the initial string for JavaScript enabled search.
402         if (savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=").equals("Custom URL")) {
403             // Get the custom URL string.  The default is "".
404             javaScriptEnabledSearchURL = savedPreferences.getString("javascript_enabled_search_custom_url", "");
405         } else {
406             // Use the string from javascript_enabled_search.
407             javaScriptEnabledSearchURL = savedPreferences.getString("javascript_enabled_search", "https://duckduckgo.com/?q=");
408         }
409
410
411         // Set homepage initial status.  The default value is "https://www.duckduckgo.com".
412         homepage = savedPreferences.getString("homepage", "https://www.duckduckgo.com");
413
414         // Set swipe to refresh initial status.  The default is true.
415         swipeToRefreshEnabled = savedPreferences.getBoolean("swipe_to_refresh_enabled", true);
416         swipeToRefresh.setEnabled(swipeToRefreshEnabled);
417
418
419         // Get the intent information that started the app.
420         final Intent intent = getIntent();
421
422         if (intent.getData() != null) {
423             // Get the intent data and convert it to a string.
424             final Uri intentUriData = intent.getData();
425             formattedUrlString = intentUriData.toString();
426         }
427
428         // If formattedUrlString is null assign the homepage to it.
429         if (formattedUrlString == null) {
430             formattedUrlString = homepage;
431         }
432
433         // Load the initial website.
434         mainWebView.loadUrl(formattedUrlString);
435
436         // Load the default favorite icon if it is null.
437         if (favoriteIcon == null) {
438             // We have to use `ContextCompat` until API >= 21.
439             Drawable favoriteIconDrawable = ContextCompat.getDrawable(getApplicationContext(), R.drawable.world);
440             BitmapDrawable favoriteIconBitmapDrawable = (BitmapDrawable) favoriteIconDrawable;
441             favoriteIcon = favoriteIconBitmapDrawable.getBitmap();
442         }
443
444         // Initialize AdView for the free flavor and request an ad.  If this is not the free flavor BannerAd.requestAd() does nothing.
445         adView = findViewById(R.id.adView);
446         BannerAd.requestAd(adView);
447     }
448
449
450     @Override
451     protected void onNewIntent(Intent intent) {
452         // Sets the new intent as the activity intent, so that any future getIntent()s pick up this one instead of creating a new activity.
453         setIntent(intent);
454
455         if (intent.getData() != null) {
456             // Get the intent data and convert it to a string.
457             final Uri intentUriData = intent.getData();
458             formattedUrlString = intentUriData.toString();
459         }
460
461         // Close the navigation drawer if it is open.
462         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
463             drawerLayout.closeDrawer(GravityCompat.START);
464         }
465
466         // Load the website.
467         mainWebView.loadUrl(formattedUrlString);
468
469         // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
470         mainWebView.requestFocus();
471     }
472
473     @Override
474     public boolean onCreateOptionsMenu(Menu menu) {
475         // Inflate the menu; this adds items to the action bar if it is present.
476         getMenuInflater().inflate(R.menu.webview_options_menu, menu);
477
478         // Set mainMenu so it can be used by onOptionsItemSelected.
479         mainMenu = menu;
480
481         // Initialize privacyIcon
482         privacyIcon = menu.findItem(R.id.toggleJavaScript);
483
484         // Get MenuItems for checkable menu items.
485         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
486         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
487         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
488         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
489
490         // Set the initial status of the privacy icon.
491         updatePrivacyIcon();
492
493         // Set the initial status of the menu item checkboxes.
494         toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
495         toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
496         toggleDomStorage.setChecked(domStorageEnabled);
497         toggleSaveFormData.setChecked(saveFormDataEnabled);
498
499         return true;
500     }
501
502     @Override
503     public boolean onPrepareOptionsMenu(Menu menu) {
504         // Only enable Third-Party Cookies if SDK >= 21 and First-Party Cookies are enabled.
505         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
506         if ((Build.VERSION.SDK_INT >= 21) && firstPartyCookiesEnabled) {
507             toggleThirdPartyCookies.setEnabled(true);
508         } else {
509             toggleThirdPartyCookies.setEnabled(false);
510         }
511
512         // Enable DOM Storage if JavaScript is enabled.
513         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
514         toggleDomStorage.setEnabled(javaScriptEnabled);
515
516         // Enable Clear Cookies if there are any.
517         MenuItem clearCookies = menu.findItem(R.id.clearCookies);
518         clearCookies.setEnabled(cookieManager.hasCookies());
519
520         // Enable Clear Form Data is there is any.
521         MenuItem clearFormData = menu.findItem(R.id.clearFormData);
522         WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
523         clearFormData.setEnabled(mainWebViewDatabase.hasFormData());
524
525         // Run all the other default commands.
526         super.onPrepareOptionsMenu(menu);
527
528         // `return true` displays the menu.
529         return true;
530     }
531
532     @Override
533     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
534     @SuppressLint("SetJavaScriptEnabled")
535     // removeAllCookies is deprecated, but it is required for API < 21.
536     @SuppressWarnings("deprecation")
537     public boolean onOptionsItemSelected(MenuItem menuItem) {
538         int menuItemId = menuItem.getItemId();
539
540         // Set the commands that relate to the menu entries.
541         switch (menuItemId) {
542             case R.id.toggleJavaScript:
543                 // Switch the status of javaScriptEnabled.
544                 javaScriptEnabled = !javaScriptEnabled;
545
546                 // Apply the new JavaScript status.
547                 mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
548
549                 // Update the privacy icon.
550                 updatePrivacyIcon();
551
552                 // Display a Snackbar.
553                 if (javaScriptEnabled) {
554                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
555                 } else {
556                     if (firstPartyCookiesEnabled) {
557                         Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
558                     } else {
559                         Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
560                     }
561                 }
562
563                 // Reload the WebView.
564                 mainWebView.reload();
565                 return true;
566
567             case R.id.toggleFirstPartyCookies:
568                 // Switch the status of firstPartyCookiesEnabled.
569                 firstPartyCookiesEnabled = !firstPartyCookiesEnabled;
570
571                 // Update the menu checkbox.
572                 menuItem.setChecked(firstPartyCookiesEnabled);
573
574                 // Apply the new cookie status.
575                 cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
576
577                 // Update the privacy icon.
578                 updatePrivacyIcon();
579
580                 // Reload the WebView.
581                 mainWebView.reload();
582                 return true;
583
584             case R.id.toggleThirdPartyCookies:
585                 if (Build.VERSION.SDK_INT >= 21) {
586                     // Switch the status of thirdPartyCookiesEnabled.
587                     thirdPartyCookiesEnabled = !thirdPartyCookiesEnabled;
588
589                     // Update the menu checkbox.
590                     menuItem.setChecked(thirdPartyCookiesEnabled);
591
592                     // Apply the new cookie status.
593                     cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
594
595                     // Reload the WebView.
596                     mainWebView.reload();
597                 } // Else do nothing because SDK < 21.
598                 return true;
599
600             case R.id.toggleDomStorage:
601                 // Switch the status of domStorageEnabled.
602                 domStorageEnabled = !domStorageEnabled;
603
604                 // Update the menu checkbox.
605                 menuItem.setChecked(domStorageEnabled);
606
607                 // Apply the new DOM Storage status.
608                 mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
609
610                 // Reload the WebView.
611                 mainWebView.reload();
612                 return true;
613
614             case R.id.toggleSaveFormData:
615                 // Switch the status of saveFormDataEnabled.
616                 saveFormDataEnabled = !saveFormDataEnabled;
617
618                 // Update the menu checkbox.
619                 menuItem.setChecked(saveFormDataEnabled);
620
621                 // Apply the new form data status.
622                 mainWebView.getSettings().setSaveFormData(saveFormDataEnabled);
623
624                 // Reload the WebView.
625                 mainWebView.reload();
626                 return true;
627
628             case R.id.clearCookies:
629                 if (Build.VERSION.SDK_INT < 21) {
630                     cookieManager.removeAllCookie();
631                 } else {
632                     cookieManager.removeAllCookies(null);
633                 }
634                 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
635                 return true;
636
637             case R.id.clearDomStorage:
638                 WebStorage webStorage = WebStorage.getInstance();
639                 webStorage.deleteAllData();
640                 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
641                 return true;
642
643             case R.id.clearFormData:
644                 WebViewDatabase mainWebViewDatabase = WebViewDatabase.getInstance(this);
645                 mainWebViewDatabase.clearFormData();
646                 mainWebView.reload();
647                 return true;
648
649             case R.id.share:
650                 Intent shareIntent = new Intent();
651                 shareIntent.setAction(Intent.ACTION_SEND);
652                 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
653                 shareIntent.setType("text/plain");
654                 startActivity(Intent.createChooser(shareIntent, "Share URL"));
655                 return true;
656
657             case R.id.addToHomescreen:
658                 // Show the CreateHomeScreenShortcut AlertDialog and name this instance "@string/create_shortcut".
659                 DialogFragment createHomeScreenShortcutDialogFragment = new CreateHomeScreenShortcut();
660                 createHomeScreenShortcutDialogFragment.show(getFragmentManager(), getResources().getString(R.string.create_shortcut));
661
662                 //Everything else will be handled by CreateHomeScreenShortcut and the associated listeners below.
663                 return true;
664
665             case R.id.refresh:
666                 mainWebView.reload();
667                 return true;
668
669             default:
670                 // Don't consume the event.
671                 return super.onOptionsItemSelected(menuItem);
672         }
673     }
674
675     @Override
676     // removeAllCookies is deprecated, but it is required for API < 21.
677     @SuppressWarnings("deprecation")
678     public boolean onNavigationItemSelected(MenuItem menuItem) {
679         int menuItemId = menuItem.getItemId();
680
681         switch (menuItemId) {
682             case R.id.home:
683                 mainWebView.loadUrl(homepage);
684                 break;
685
686             case R.id.back:
687                 if (mainWebView.canGoBack()) {
688                     mainWebView.goBack();
689                 }
690                 break;
691
692             case R.id.forward:
693                 if (mainWebView.canGoForward()) {
694                     mainWebView.goForward();
695                 }
696                 break;
697
698             case R.id.bookmarks:
699                 // Launch BookmarksActivity.
700                 Intent bookmarksIntent = new Intent(this, BookmarksActivity.class);
701                 startActivity(bookmarksIntent);
702                 break;
703
704             case R.id.downloads:
705                 // Launch the system Download Manager.
706                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
707
708                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
709                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
710
711                 startActivity(downloadManagerIntent);
712                 break;
713
714             case R.id.settings:
715                 // Launch `SettingsActivity`.
716                 Intent settingsIntent = new Intent(this, SettingsActivity.class);
717                 startActivity(settingsIntent);
718                 break;
719
720             case R.id.guide:
721                 // Launch `GuideActivity`.
722                 Intent guideIntent = new Intent(this, GuideActivity.class);
723                 startActivity(guideIntent);
724                 break;
725
726             case R.id.about:
727                 // Launch `AboutActivity`.
728                 Intent aboutIntent = new Intent(this, AboutActivity.class);
729                 startActivity(aboutIntent);
730                 break;
731
732             case R.id.clearAndExit:
733                 // Clear cookies.  The commands changed slightly in API 21.
734                 if (Build.VERSION.SDK_INT >= 21) {
735                     cookieManager.removeAllCookies(null);
736                 } else {
737                     cookieManager.removeAllCookie();
738                 }
739
740                 // Clear DOM storage.
741                 WebStorage domStorage = WebStorage.getInstance();
742                 domStorage.deleteAllData();
743
744                 // Clear form data.
745                 WebViewDatabase formData = WebViewDatabase.getInstance(this);
746                 formData.clearFormData();
747
748                 // Clear cache.  The argument of "true" includes disk files.
749                 mainWebView.clearCache(true);
750
751                 // Clear the back/forward history.
752                 mainWebView.clearHistory();
753
754                 formattedUrlString = null;
755
756                 // Destroy the internal state of the webview.
757                 mainWebView.destroy();
758
759                 // Close Privacy Browser.  finishAndRemoveTask also removes Privacy Browser from the recent app list.
760                 if (Build.VERSION.SDK_INT >= 21) {
761                     finishAndRemoveTask();
762                 } else {
763                     finish();
764                 }
765                 break;
766
767             default:
768                 break;
769         }
770
771         // Close the navigation drawer.
772         drawerLayout.closeDrawer(GravityCompat.START);
773         return true;
774     }
775
776     @Override
777     public void onPostCreate(Bundle savedInstanceState) {
778         super.onPostCreate(savedInstanceState);
779
780         // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
781         drawerToggle.syncState();
782     }
783
784     @Override
785     public void onConfigurationChanged(Configuration newConfig) {
786         super.onConfigurationChanged(newConfig);
787
788         // Reload the ad if this is the free flavor.
789         BannerAd.reloadAfterRotate(adView, getApplicationContext(), getString(R.string.ad_id));
790
791         // Reinitialize the adView variable, as the View will have been removed and re-added in the free flavor by BannerAd.reloadAfterRotate().
792         adView = findViewById(R.id.adView);
793     }
794
795     @Override
796     public void onCancelCreateHomeScreenShortcut(DialogFragment dialogFragment) {
797         // Do nothing because the user selected "Cancel".
798     }
799
800     @Override
801     public void onCreateHomeScreenShortcut(DialogFragment dialogFragment) {
802         // Get shortcutNameEditText from the alert dialog.
803         EditText shortcutNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.shortcut_name_edittext);
804
805         // Create the bookmark shortcut based on formattedUrlString.
806         Intent bookmarkShortcut = new Intent();
807         bookmarkShortcut.setAction(Intent.ACTION_VIEW);
808         bookmarkShortcut.setData(Uri.parse(formattedUrlString));
809
810         // Place the bookmark shortcut on the home screen.
811         Intent placeBookmarkShortcut = new Intent();
812         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
813         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
814         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
815         placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
816         sendBroadcast(placeBookmarkShortcut);
817     }
818
819     // Override onBackPressed to handle the navigation drawer and mainWebView.
820     @Override
821     public void onBackPressed() {
822         final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
823
824         // Close the navigation drawer if it is available.  GravityCompat.START is the drawer on the left on Left-to-Right layout text.
825         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
826             drawerLayout.closeDrawer(GravityCompat.START);
827         } else {
828             // Load the previous URL if available.
829             assert mainWebView != null; //This assert removes the incorrect warning in Android Studio on the following line that mainWebView might be null.
830             if (mainWebView.canGoBack()) {
831                 mainWebView.goBack();
832             } else {
833                 // Pass onBackPressed to the system.
834                 super.onBackPressed();
835             }
836         }
837     }
838
839     @Override
840     public void onPause() {
841         // We need to pause the adView or it will continue to consume resources in the background on the free flavor.
842         BannerAd.pauseAd(adView);
843
844         super.onPause();
845     }
846
847     @Override
848     public void onResume() {
849         super.onResume();
850
851         // We need to resume the adView for the free flavor.
852         BannerAd.resumeAd(adView);
853     }
854
855     private void loadUrlFromTextBox() throws UnsupportedEncodingException {
856         // Get the text from urlTextBox and convert it to a string.  trim() removes white spaces from the beginning and end of the string.
857         String unformattedUrlString = urlTextBox.getText().toString().trim();
858
859         URL unformattedUrl = null;
860         Uri.Builder formattedUri = new Uri.Builder();
861
862         // Check to see if unformattedUrlString is a valid URL.  Otherwise, convert it into a Duck Duck Go search.
863         if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
864             // Add http:// at the beginning if it is missing.  Otherwise the app will segfault.
865             if (!unformattedUrlString.startsWith("http")) {
866                 unformattedUrlString = "http://" + unformattedUrlString;
867             }
868
869             // 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.
870             try {
871                 unformattedUrl = new URL(unformattedUrlString);
872             } catch (MalformedURLException e) {
873                 e.printStackTrace();
874             }
875
876             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
877             final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
878             final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
879             final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
880             final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
881             final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
882
883             formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
884             formattedUrlString = formattedUri.build().toString();
885         } else {
886             // Sanitize the search input and convert it to a DuckDuckGo search.
887             final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
888
889             // Use the correct search URL based on javaScriptEnabled.
890             if (javaScriptEnabled) {
891                 formattedUrlString = javaScriptEnabledSearchURL + encodedUrlString;
892             } else { // JavaScript is disabled.
893                 formattedUrlString = javaScriptDisabledSearchURL + encodedUrlString;
894             }
895         }
896
897         mainWebView.loadUrl(formattedUrlString);
898
899         // Hides the keyboard so we can see the webpage.
900         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
901         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
902     }
903
904     private void updatePrivacyIcon() {
905         if (javaScriptEnabled) {
906             privacyIcon.setIcon(R.drawable.javascript_enabled);
907         } else {
908             if (firstPartyCookiesEnabled) {
909                 privacyIcon.setIcon(R.drawable.warning);
910             } else {
911                 privacyIcon.setIcon(R.drawable.privacy_mode);
912             }
913         }
914     }
915 }