Add a SharedPreferencesChangeListener.
[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.DownloadManager;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.res.Configuration;
28 import android.graphics.Bitmap;
29 import android.net.Uri;
30 import android.os.Build;
31 import android.os.Bundle;
32 import android.preference.PreferenceManager;
33 import android.support.design.widget.NavigationView;
34 import android.support.design.widget.Snackbar;
35 import android.support.v4.app.DialogFragment;
36 import android.support.v4.view.GravityCompat;
37 import android.support.v4.widget.DrawerLayout;
38 import android.support.v4.widget.SwipeRefreshLayout;
39 import android.support.v7.app.ActionBar;
40 import android.support.v7.app.ActionBarDrawerToggle;
41 import android.support.v7.app.AppCompatActivity;
42 import android.support.v7.app.AppCompatDialogFragment;
43 import android.support.v7.widget.Toolbar;
44 import android.util.Patterns;
45 import android.view.KeyEvent;
46 import android.view.Menu;
47 import android.view.MenuItem;
48 import android.view.View;
49 import android.view.inputmethod.InputMethodManager;
50 import android.webkit.CookieManager;
51 import android.webkit.DownloadListener;
52 import android.webkit.WebChromeClient;
53 import android.webkit.WebStorage;
54 import android.webkit.WebView;
55 import android.webkit.WebViewClient;
56 import android.widget.EditText;
57 import android.widget.FrameLayout;
58 import android.widget.ImageView;
59 import android.widget.ProgressBar;
60
61 import java.io.UnsupportedEncodingException;
62 import java.net.MalformedURLException;
63 import java.net.URL;
64 import java.net.URLEncoder;
65
66 // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21.
67 public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener {
68     // favoriteIcon is public static so it can be accessed from CreateHomeScreenShortcut.
69     public static Bitmap favoriteIcon;
70     // mainWebView is public static so it can be accessed from AboutDialog and SettingsFragment.  It is also used in onCreate(), onOptionsItemSelected(), onNavigationItemSelected(), and loadUrlFromTextBox().
71     public static WebView mainWebView;
72
73     // mainMenu is public static so it can be accessed from SettingsFragment.  It is also used in onCreateOptionsMenu() and onOptionsItemSelected().
74     public static Menu mainMenu;
75     // cookieManager is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onOptionsItemSelected(), and onNavigationItemSelected().
76     public static CookieManager cookieManager;
77     // javaScriptEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), onOptionsItemSelected(), and loadUrlFromTextBox().
78     public static boolean javaScriptEnabled;
79     // firstPartyCookiesEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), onPrepareOptionsMenu(), and onOptionsItemSelected().
80     public static boolean firstPartyCookiesEnabled;
81     // thirdPartyCookiesEnabled is uesd in onCreate(), onCreateOptionsMenu(), onPrepareOptionsMenu(), and onOptionsItemSelected().
82     public static boolean thirdPartyCookiesEnabled;
83     // domStorageEnabled is public static so it can be accessed from SettingsFragment.  It is also used in onCreate(), onCreateOptionsMenu(), and onOptionsItemSelected().
84     public static boolean domStorageEnabled;
85     // homepage is public static so it can be accessed from  SettingsFragment.  It is also used in onCreate() and onOptionsItemSelected().
86     public static String homepage;
87
88     // drawerToggle is used in onCreate(), onPostCreate(), onConfigurationChanged(), onNewIntent(), and onNavigationItemSelected().
89     private ActionBarDrawerToggle drawerToggle;
90     // drawerLayout is used in onCreate(), onNewIntent(), and onBackPressed().
91     private DrawerLayout drawerLayout;
92     // formattedUrlString is used in onCreate(), onOptionsItemSelected(), onCreateHomeScreenShortcutCreate(), and loadUrlFromTextBox().
93     private String formattedUrlString;
94
95     // urlTextBox is used in onCreate(), onOptionsItemSelected(), and loadUrlFromTextBox().
96     private EditText urlTextBox;
97
98     @Override
99     // 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.
100     @SuppressLint("SetJavaScriptEnabled")
101     protected void onCreate(Bundle savedInstanceState) {
102         super.onCreate(savedInstanceState);
103         setContentView(R.layout.coordinator_layout);
104
105         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
106         Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar);
107         setSupportActionBar(supportAppBar);
108
109         final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
110
111         // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21.
112         final ActionBar appBar = getSupportActionBar();
113
114         // Setup AdView for the free flavor.
115         final View adView = findViewById(R.id.adView);
116
117         // Implement swipe to refresh
118         final SwipeRefreshLayout swipeToRefresh = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
119         swipeToRefresh.setColorSchemeResources(R.color.blue);
120         swipeToRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
121             @Override
122             public void onRefresh() {
123                 mainWebView.reload();
124             }
125         });
126
127         mainWebView = (WebView) findViewById(R.id.mainWebView);
128
129         if (appBar != null) {
130             // Add the custom url_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
131             appBar.setCustomView(R.layout.url_bar);
132             appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
133
134             // Set the "go" button on the keyboard to load the URL in urlTextBox.
135             urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox);
136             urlTextBox.setOnKeyListener(new View.OnKeyListener() {
137                 public boolean onKey(View v, int keyCode, KeyEvent event) {
138                     // If the event is a key-down event on the "enter" button, load the URL.
139                     if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
140                         // Load the URL into the mainWebView and consume the event.
141                         try {
142                             loadUrlFromTextBox();
143                         } catch (UnsupportedEncodingException e) {
144                             e.printStackTrace();
145                         }
146                         // If the enter key was pressed, consume the event.
147                         return true;
148                     } else {
149                         // If any other key was pressed, do not consume the event.
150                         return false;
151                     }
152                 }
153             });
154         }
155
156         // Create the navigation drawer.
157         drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
158         // The DrawerTitle identifies the drawer in accessibility mode.
159         drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer));
160
161         // Listen for touches on the navigation menu.
162         final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView);
163         navigationView.setNavigationItemSelectedListener(this);
164
165         // drawerToggle creates the hamburger icon at the start of the AppBar.
166         drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation);
167
168         mainWebView.setWebViewClient(new WebViewClient() {
169             // shouldOverrideUrlLoading makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
170             @Override
171             public boolean shouldOverrideUrlLoading(WebView view, String url) {
172                 mainWebView.loadUrl(url);
173                 return true;
174             }
175
176             // Update the URL in urlTextBox when the page starts to load.
177             @Override
178             public void onPageStarted(WebView view, String url, Bitmap favicon) {
179                 urlTextBox.setText(url);
180             }
181
182             // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
183             @Override
184             public void onPageFinished(WebView view, String url) {
185                 formattedUrlString = url;
186
187                 // Only update urlTextBox if the user is not typing in it.
188                 if (!urlTextBox.hasFocus()) {
189                     urlTextBox.setText(formattedUrlString);
190                 }
191             }
192         });
193
194         mainWebView.setWebChromeClient(new WebChromeClient() {
195             // Update the progress bar when a page is loading.
196             @Override
197             public void onProgressChanged(WebView view, int progress) {
198                 // Make sure that appBar is not null.
199                 if (appBar != null) {
200                     ProgressBar progressBar = (ProgressBar) appBar.getCustomView().findViewById(R.id.progressBar);
201                     progressBar.setProgress(progress);
202                     if (progress < 100) {
203                         progressBar.setVisibility(View.VISIBLE);
204                     } else {
205                         progressBar.setVisibility(View.GONE);
206
207                         //Stop the SwipeToRefresh indicator if it is running
208                         swipeToRefresh.setRefreshing(false);
209                     }
210                 }
211             }
212
213             // Set the favorite icon when it changes.
214             @Override
215             public void onReceivedIcon(WebView view, Bitmap icon) {
216                 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
217                 favoriteIcon = icon;
218
219                 // Place the favorite icon in the appBar if it is not null.
220                 if (appBar != null) {
221                     ImageView imageViewFavoriteIcon = (ImageView) appBar.getCustomView().findViewById(R.id.favoriteIcon);
222                     imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
223                 }
224             }
225
226             // Enter full screen video
227             @Override
228             public void onShowCustomView(View view, CustomViewCallback callback) {
229                 if (appBar != null) {
230                     appBar.hide();
231                 }
232
233                 // Show the fullScreenVideoFrameLayout.
234                 fullScreenVideoFrameLayout.addView(view);
235                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
236
237                 // Hide the mainWebView.
238                 mainWebView.setVisibility(View.GONE);
239
240                 // Hide the ad if this is the free flavor.
241                 BannerAd.hideAd(adView);
242
243                 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
244                 ** SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
245                 ** SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
246                 */
247
248                 // Set the one flag supported by API >= 14.
249                 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
250
251                 // Set the two flags that are supported by API >= 16.
252                 if (Build.VERSION.SDK_INT >= 16) {
253                     view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
254                 }
255
256                 // Set all three flags that are supported by API >= 19.
257                 if (Build.VERSION.SDK_INT >= 19) {
258                     view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
259                 }
260             }
261
262             // Exit full screen video
263             public void onHideCustomView() {
264                 if (appBar != null) {
265                     appBar.show();
266                 }
267
268                 // Show the mainWebView.
269                 mainWebView.setVisibility(View.VISIBLE);
270
271                 // Show the ad if this is the free flavor.
272                 BannerAd.showAd(adView);
273
274                 // Hide the fullScreenVideoFrameLayout.
275                 fullScreenVideoFrameLayout.removeAllViews();
276                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
277             }
278         });
279
280         // Allow the downloading of files.
281         mainWebView.setDownloadListener(new DownloadListener() {
282             // Launch the Android download manager when a link leads to a download.
283             @Override
284             public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
285                 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
286                 DownloadManager.Request requestUri = new DownloadManager.Request(Uri.parse(url));
287
288                 // Add the URL as the description for the download.
289                 requestUri.setDescription(url);
290
291                 // Show the download notification after the download is completed.
292                 requestUri.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
293
294                 // Initiate the download and display a Snackbar.
295                 downloadManager.enqueue(requestUri);
296                 Snackbar.make(findViewById(R.id.mainWebView), R.string.download_started, Snackbar.LENGTH_SHORT).show();
297             }
298         });
299
300         // Allow pinch to zoom.
301         mainWebView.getSettings().setBuiltInZoomControls(true);
302
303         // Hide zoom controls.
304         mainWebView.getSettings().setDisplayZoomControls(false);
305
306         // Initialize the default preference values the first time the program is run.
307         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
308
309         // Get the shared preference values.
310         SharedPreferences savedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
311
312         // Set JavaScript initial status.
313         javaScriptEnabled = savedPreferences.getBoolean("javascript_enabled", false);
314         mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
315
316         // Initialize cookieManager.
317         cookieManager = CookieManager.getInstance();
318
319         // Set cookies initial status.
320         firstPartyCookiesEnabled = savedPreferences.getBoolean("first_party_cookies_enabled", false);
321         cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
322
323         // Set third-party cookies initial status if API >= 21.
324         if (Build.VERSION.SDK_INT >= 21) {
325             thirdPartyCookiesEnabled = savedPreferences.getBoolean("third_party_cookies_enabled", false);
326             cookieManager.setAcceptThirdPartyCookies(mainWebView, thirdPartyCookiesEnabled);
327         }
328
329         // Set DOM storage initial status.
330         domStorageEnabled = savedPreferences.getBoolean("dom_storage_enabled", false);
331         mainWebView.getSettings().setDomStorageEnabled(domStorageEnabled);
332
333         // Set homepage initial status.
334         homepage = savedPreferences.getString("homepage", "https://www.duckduckgo.com");
335
336         // Get the intent information that started the app.
337         final Intent intent = getIntent();
338
339         if (intent.getData() != null) {
340             // Get the intent data and convert it to a string.
341             final Uri intentUriData = intent.getData();
342             formattedUrlString = intentUriData.toString();
343         }
344
345         // If formattedUrlString is null assign the homepage to it.
346         if (formattedUrlString == null) {
347             formattedUrlString = homepage;
348         }
349
350         // Load the initial website.
351         mainWebView.loadUrl(formattedUrlString);
352
353         // Load the ad if this is the free flavor.
354         BannerAd.requestAd(adView);
355     }
356
357     @Override
358     protected void onNewIntent(Intent intent) {
359         // Sets the new intent as the activity intent, so that any future getIntent()s pick up this one instead of creating a new activity.
360         setIntent(intent);
361
362         if (intent.getData() != null) {
363             // Get the intent data and convert it to a string.
364             final Uri intentUriData = intent.getData();
365             formattedUrlString = intentUriData.toString();
366         }
367
368         // Close the navigation drawer if it is open.
369         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
370             drawerLayout.closeDrawer(GravityCompat.START);
371         }
372
373         // Load the website.
374         mainWebView.loadUrl(formattedUrlString);
375
376         // Clear the keyboard if displayed and remove the focus on the urlTextBar if it has it.
377         mainWebView.requestFocus();
378     }
379
380     @Override
381     public boolean onCreateOptionsMenu(Menu menu) {
382         // Inflate the menu; this adds items to the action bar if it is present.
383         getMenuInflater().inflate(R.menu.menu_options, menu);
384
385         // Set mainMenu so it can be used by onOptionsItemSelected.
386         mainMenu = menu;
387
388         // Get MenuItems for checkable menu items.
389         MenuItem toggleJavaScript = menu.findItem(R.id.toggleJavaScript);
390         MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies);
391         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
392         MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage);
393         /* toggleSaveFormData does nothing until database storage is implemented.
394         MenuItem toggleSaveFormData = menu.findItem(R.id.toggleSaveFormData);
395         */
396
397         // Set the initial icon for toggleJavaScript
398         if (javaScriptEnabled) {
399             toggleJavaScript.setIcon(R.drawable.javascript_enabled);
400         } else {
401             if (domStorageEnabled || firstPartyCookiesEnabled) {
402                 toggleJavaScript.setIcon(R.drawable.warning);
403             } else {
404                 toggleJavaScript.setIcon(R.drawable.privacy_mode);
405             }
406         }
407
408         // Set the initial status of the menu item checkboxes.
409         toggleFirstPartyCookies.setChecked(firstPartyCookiesEnabled);
410         toggleThirdPartyCookies.setChecked(thirdPartyCookiesEnabled);
411         toggleDomStorage.setChecked(domStorageEnabled);
412         /* toggleSaveFormData does nothing until database storage is implemented.
413         toggleSaveFormData.setChecked(saveFormDataEnabled);
414         */
415
416         return true;
417     }
418
419     @Override
420     public boolean onPrepareOptionsMenu(Menu menu) {
421         // Only enable Third-Party Cookies if SDK >= 21 and First-Party Cookies are enabled.
422         MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies);
423         if ((Build.VERSION.SDK_INT >= 21) && firstPartyCookiesEnabled) {
424             toggleThirdPartyCookies.setEnabled(true);
425         } else {
426             toggleThirdPartyCookies.setEnabled(false);
427         }
428
429         // Enable Clear Cookies if there are any.
430         MenuItem clearCookies = menu.findItem(R.id.clearCookies);
431         clearCookies.setEnabled(cookieManager.hasCookies());
432
433         // Run all the other default commands.
434         super.onPrepareOptionsMenu(menu);
435
436         // return true displays the menu.
437         return true;
438     }
439
440     @Override
441     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
442     @SuppressLint("SetJavaScriptEnabled")
443     // removeAllCookies is deprecated, but it is required for API < 21.
444     @SuppressWarnings("deprecation")
445     public boolean onOptionsItemSelected(MenuItem menuItem) {
446         int menuItemId = menuItem.getItemId();
447
448         // Some options need to update the drawable for toggleJavaScript.
449         MenuItem toggleJavaScript = mainMenu.findItem(R.id.toggleJavaScript);
450
451         // Set the commands that relate to the menu entries.
452         switch (menuItemId) {
453             case R.id.toggleJavaScript:
454                 if (javaScriptEnabled) {
455                     javaScriptEnabled = false;
456                     mainWebView.getSettings().setJavaScriptEnabled(false);
457                     mainWebView.reload();
458
459                     // Update the toggleJavaScript icon and display a snackbar.
460                     if (domStorageEnabled || firstPartyCookiesEnabled) {
461                         menuItem.setIcon(R.drawable.warning);
462                         Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_disabled, Snackbar.LENGTH_SHORT).show();
463                     } else {
464                         menuItem.setIcon(R.drawable.privacy_mode);
465                         Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
466                     }
467                 } else {
468                     javaScriptEnabled = true;
469                     menuItem.setIcon(R.drawable.javascript_enabled);
470                     mainWebView.getSettings().setJavaScriptEnabled(true);
471                     mainWebView.reload();
472                     Snackbar.make(findViewById(R.id.mainWebView), R.string.javascript_enabled, Snackbar.LENGTH_SHORT).show();
473                 }
474                 return true;
475
476             case R.id.toggleFirstPartyCookies:
477                 if (firstPartyCookiesEnabled) {
478                     firstPartyCookiesEnabled = false;
479                     menuItem.setChecked(false);
480                     cookieManager.setAcceptCookie(false);
481                     mainWebView.reload();
482
483                     // Update the toggleJavaScript icon if appropriate and display a snackbar.
484                     if (!javaScriptEnabled) {
485                         if (domStorageEnabled) {
486                             toggleJavaScript.setIcon(R.drawable.warning);
487                             Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
488                         } else {
489                             toggleJavaScript.setIcon(R.drawable.privacy_mode);
490                             Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
491                         }
492                     } else {
493                         Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
494                     }
495                 } else {
496                     firstPartyCookiesEnabled = true;
497                     menuItem.setChecked(true);
498                     cookieManager.setAcceptCookie(true);
499                     mainWebView.reload();
500
501                     // Update the toggleJavaScript icon if appropriate.
502                     if (!javaScriptEnabled) {
503                         toggleJavaScript.setIcon(R.drawable.warning);
504                     } // Else do nothing because JavaScript is enabled.
505
506                     Snackbar.make(findViewById(R.id.mainWebView), R.string.first_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
507                 }
508                 return true;
509
510             case R.id.toggleThirdPartyCookies:
511                 if (Build.VERSION.SDK_INT >= 21) {
512                     if (thirdPartyCookiesEnabled) {
513                         thirdPartyCookiesEnabled = false;
514                         menuItem.setChecked(false);
515                         cookieManager.setAcceptThirdPartyCookies(mainWebView, false);
516                         mainWebView.reload();
517
518                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_disabled, Snackbar.LENGTH_SHORT).show();
519                     } else {
520                         thirdPartyCookiesEnabled = true;
521                         menuItem.setChecked(true);
522                         cookieManager.setAcceptThirdPartyCookies(mainWebView, true);
523                         mainWebView.reload();
524
525                         Snackbar.make(findViewById(R.id.mainWebView), R.string.third_party_cookies_enabled, Snackbar.LENGTH_SHORT).show();
526                     }
527                 } // Else do nothing because SDK < 21.
528                 return true;
529
530             case R.id.toggleDomStorage:
531                 if (domStorageEnabled) {
532                     domStorageEnabled = false;
533                     menuItem.setChecked(false);
534                     mainWebView.getSettings().setDomStorageEnabled(false);
535                     mainWebView.reload();
536
537                     // Update the toggleJavaScript icon if appropriate and display a snackbar.
538                     if (!javaScriptEnabled) {
539                         if (firstPartyCookiesEnabled) {
540                             toggleJavaScript.setIcon(R.drawable.warning);
541                             Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
542                         } else {
543                             toggleJavaScript.setIcon(R.drawable.privacy_mode);
544                             Snackbar.make(findViewById(R.id.mainWebView), R.string.privacy_mode, Snackbar.LENGTH_SHORT).show();
545                         }
546                     }else {
547                         Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_disabled, Snackbar.LENGTH_SHORT).show();
548                     }
549                 } else {
550                     domStorageEnabled = true;
551                     menuItem.setChecked(true);
552                     mainWebView.getSettings().setDomStorageEnabled(true);
553                     mainWebView.reload();
554
555                     // Update the toggleJavaScript icon if appropriate.
556                     if (!javaScriptEnabled) {
557                         toggleJavaScript.setIcon(R.drawable.warning);
558                     } // Else Do nothing because JavaScript is enabled.
559
560                     Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_enabled, Snackbar.LENGTH_SHORT).show();
561                 }
562                 return true;
563
564             case R.id.clearCookies:
565                 if (Build.VERSION.SDK_INT < 21) {
566                     cookieManager.removeAllCookie();
567                 } else {
568                     cookieManager.removeAllCookies(null);
569                 }
570                 Snackbar.make(findViewById(R.id.mainWebView), R.string.cookies_deleted, Snackbar.LENGTH_SHORT).show();
571                 return true;
572
573             case R.id.clearDomStorage:
574                 WebStorage webStorage = WebStorage.getInstance();
575                 webStorage.deleteAllData();
576                 Snackbar.make(findViewById(R.id.mainWebView), R.string.dom_storage_deleted, Snackbar.LENGTH_SHORT).show();
577                 return true;
578
579             case R.id.share:
580                 Intent shareIntent = new Intent();
581                 shareIntent.setAction(Intent.ACTION_SEND);
582                 shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
583                 shareIntent.setType("text/plain");
584                 startActivity(Intent.createChooser(shareIntent, "Share URL"));
585                 return true;
586
587             case R.id.addToHomescreen:
588                 // Show the CreateHomeScreenShortcut AlertDialog and name this instance createShortcut.
589                 AppCompatDialogFragment shortcutDialog = new CreateHomeScreenShortcut();
590                 shortcutDialog.show(getSupportFragmentManager(), "createShortcut");
591
592                 //Everything else will be handled by CreateHomeScreenShortcut and the associated listeners below.
593                 return true;
594
595             default:
596                 return super.onOptionsItemSelected(menuItem);
597         }
598     }
599
600     @Override
601     // removeAllCookies is deprecated, but it is required for API < 21.
602     @SuppressWarnings("deprecation")
603     public boolean onNavigationItemSelected(MenuItem menuItem) {
604         int menuItemId = menuItem.getItemId();
605
606         switch (menuItemId) {
607             case R.id.home:
608                 mainWebView.loadUrl(homepage);
609                 break;
610
611             case R.id.back:
612                 if (mainWebView.canGoBack()) {
613                     mainWebView.goBack();
614                 }
615                 break;
616
617             case R.id.forward:
618                 if (mainWebView.canGoForward()) {
619                     mainWebView.goForward();
620                 }
621                 break;
622
623             case R.id.downloads:
624                 // Launch the system Download Manager.
625                 Intent downloadManagerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
626
627                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
628                 downloadManagerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
629
630                 startActivity(downloadManagerIntent);
631                 break;
632
633             case R.id.settings:
634                 // Launch PreferenceFragment.
635                 Intent intent = new Intent(this, SettingsActivity.class);
636                 startActivity(intent);
637                 break;
638
639             case R.id.about:
640                 // Show the AboutDialog AlertDialog and name this instance aboutDialog.
641                 AppCompatDialogFragment aboutDialog = new AboutDialog();
642                 aboutDialog.show(getSupportFragmentManager(), "aboutDialog");
643                 break;
644
645             case R.id.clearAndExit:
646                 // Clear DOM storage.
647                 WebStorage domStorage = WebStorage.getInstance();
648                 domStorage.deleteAllData();
649
650                 // Clear cookies.  The commands changed slightly in API 21.
651                 if (Build.VERSION.SDK_INT >= 21) {
652                     cookieManager.removeAllCookies(null);
653                 } else {
654                     cookieManager.removeAllCookie();
655                 }
656
657                 // Destroy the internal state of the webview.
658                 mainWebView.destroy();
659
660                 // Close Privacy Browser.  finishAndRemoveTask also removes Privacy Browser from the recent app list.
661                 if (Build.VERSION.SDK_INT >= 21) {
662                     finishAndRemoveTask();
663                 } else {
664                     finish();
665                 }
666                 break;
667
668             default:
669                 break;
670         }
671
672         // Close the navigation drawer.
673         drawerLayout.closeDrawer(GravityCompat.START);
674         return true;
675     }
676
677     @Override
678     public void onPostCreate(Bundle savedInstanceState) {
679         super.onPostCreate(savedInstanceState);
680
681         // Sync the state of the DrawerToggle after onRestoreInstanceState has finished.
682         drawerToggle.syncState();
683     }
684
685     @Override
686     public void onConfigurationChanged(Configuration newConfig) {
687         super.onConfigurationChanged(newConfig);
688
689         // Update the status of the drawerToggle icon.
690         drawerToggle.onConfigurationChanged(newConfig);
691     }
692
693     @Override
694     public void onCreateHomeScreenShortcutCancel(DialogFragment dialog) {
695         // Do nothing because the user selected "Cancel".
696     }
697
698     @Override
699     public void onCreateHomeScreenShortcutCreate(DialogFragment dialog) {
700         // Get shortcutNameEditText from the alert dialog.
701         EditText shortcutNameEditText = (EditText) dialog.getDialog().findViewById(R.id.shortcutNameEditText);
702
703         // Create the bookmark shortcut based on formattedUrlString.
704         Intent bookmarkShortcut = new Intent();
705         bookmarkShortcut.setAction(Intent.ACTION_VIEW);
706         bookmarkShortcut.setData(Uri.parse(formattedUrlString));
707
708         // Place the bookmark shortcut on the home screen.
709         Intent placeBookmarkShortcut = new Intent();
710         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
711         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
712         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
713         placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
714         sendBroadcast(placeBookmarkShortcut);
715     }
716
717     // Override onBackPressed to handle the navigation drawer and mainWebView.
718     @Override
719     public void onBackPressed() {
720         final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
721
722         // Close the navigation drawer if it is available.  GravityCompat.START is the drawer on the left on Left-to-Right layout text.
723         if (drawerLayout.isDrawerVisible(GravityCompat.START)) {
724             drawerLayout.closeDrawer(GravityCompat.START);
725         } else {
726             // Load the previous URL if available.
727             if (mainWebView.canGoBack()) {
728                 mainWebView.goBack();
729             } else {
730                 // Pass onBackPressed to the system.
731                 super.onBackPressed();
732             }
733         }
734     }
735
736     private void loadUrlFromTextBox() throws UnsupportedEncodingException {
737         // Get the text from urlTextBox and convert it to a string.
738         String unformattedUrlString = urlTextBox.getText().toString();
739         URL unformattedUrl = null;
740         Uri.Builder formattedUri = new Uri.Builder();
741
742         // Check to see if unformattedUrlString is a valid URL.  Otherwise, convert it into a Duck Duck Go search.
743         if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
744             // Add http:// at the beginning if it is missing.  Otherwise the app will segfault.
745             if (!unformattedUrlString.startsWith("http")) {
746                 unformattedUrlString = "http://" + unformattedUrlString;
747             }
748
749             // 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.
750             try {
751                 unformattedUrl = new URL(unformattedUrlString);
752             } catch (MalformedURLException e) {
753                 e.printStackTrace();
754             }
755
756             // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
757             final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
758             final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
759             final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
760             final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
761             final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
762
763             formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
764             formattedUrlString = formattedUri.build().toString();
765         } else {
766             // Sanitize the search input and convert it to a DuckDuckGo search.
767             final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
768
769             // Use the correct search URL based on javaScriptEnabled.
770             if (javaScriptEnabled) {
771                 formattedUrlString = "https://duckduckgo.com/?q=" + encodedUrlString;
772             } else {
773                 formattedUrlString = "https://duckduckgo.com/html/?q=" + encodedUrlString;
774             }
775         }
776
777         mainWebView.loadUrl(formattedUrlString);
778
779         // Hides the keyboard so we can see the webpage.
780         InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
781         inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
782     }
783 }