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