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