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