Add a menu item to toggle DOM Storage.
[PrivacyBrowser.git] / app / src / main / java / com / stoutner / privacybrowser / Webview.java
1 /**
2  * Copyright 2015-2016 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of 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.annotation.TargetApi;
24 import android.app.Activity;
25 import android.app.DownloadManager;
26 import android.content.ClipData;
27 import android.content.ClipboardManager;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.graphics.Bitmap;
31 import android.net.Uri;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.support.v4.app.DialogFragment;
35 import android.support.v7.app.ActionBar;
36 import android.support.v7.app.AppCompatActivity;
37 import android.support.v7.app.AppCompatDialogFragment;
38 import android.util.Patterns;
39 import android.view.KeyEvent;
40 import android.view.Menu;
41 import android.view.MenuItem;
42 import android.view.View;
43 import android.view.inputmethod.InputMethodManager;
44 import android.webkit.DownloadListener;
45 import android.webkit.WebChromeClient;
46 import android.webkit.WebView;
47 import android.webkit.WebViewClient;
48 import android.widget.EditText;
49 import android.widget.FrameLayout;
50 import android.widget.ImageView;
51 import android.widget.ProgressBar;
52 import android.widget.Toast;
53 import java.io.UnsupportedEncodingException;
54 import java.net.MalformedURLException;
55 import java.net.URL;
56 import java.net.URLEncoder;
57
58 public class Webview extends AppCompatActivity implements CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener {
59     // favoriteIcon is public static so it can be accessed from CreateHomeScreenShortcut.
60     public static Bitmap favoriteIcon;
61
62     // mainWebView is used in onCreate and onOptionsItemSelected.
63     private WebView mainWebView;
64     // formattedUrlString is used in onCreate, onOptionsItemSelected, onCreateHomeScreenShortcutCreate, and loadUrlFromTextBox.
65     private String formattedUrlString;
66     // homepage is used in onCreate and onOptionsItemSelected.
67     private String homepage = "https://www.duckduckgo.com/";
68     // enableJavaScript is used in onCreate and onOptionsItemSelected.
69     private boolean enableJavaScript;
70     // enableDomStorage is used in onCreate and onOptionsItemSelected.
71     private boolean enableDomStorage;
72     // actionBar is used in onCreate and onOptionsItemSelected.
73     private ActionBar actionBar;
74
75     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
76     @SuppressLint("SetJavaScriptEnabled")
77
78     @Override
79     protected void onCreate(Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81         setContentView(R.layout.activity_webview);
82
83         final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout);
84         final Activity mainWebViewActivity = this;
85
86         mainWebView = (WebView) findViewById(R.id.mainWebView);
87         actionBar = getSupportActionBar();
88
89         if (actionBar != null) {
90             // Remove the title from the action bar.
91             actionBar.setDisplayShowTitleEnabled(false);
92
93             // Add the custom app_bar layout, which shows the favoriteIcon, urlTextBar, and progressBar.
94             actionBar.setCustomView(R.layout.app_bar);
95             actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
96
97             // Set the "go" button on the keyboard to load the URL in urlTextBox.
98             EditText urlTextBox = (EditText) actionBar.getCustomView().findViewById(R.id.urlTextBox);
99             urlTextBox.setOnKeyListener(new View.OnKeyListener() {
100                 public boolean onKey(View v, int keyCode, KeyEvent event) {
101                     // If the event is a key-down event on the "enter" button, load the URL.
102                     if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
103                         // Load the URL into the mainWebView and consume the event.
104                         try {
105                             loadUrlFromTextBox();
106                         } catch (UnsupportedEncodingException e) {
107                             e.printStackTrace();
108                         }
109                         // If the enter key was pressed, consume the event.
110                         return true;
111                     } else {
112                         // If any other key was pressed, do not consume the event.
113                         return false;
114                     }
115                 }
116             });
117         }
118
119         mainWebView.setWebViewClient(new WebViewClient() {
120             // shouldOverrideUrlLoading makes this WebView the default handler for URLs inside the app, so that links are not kicked out to other apps.
121             @Override
122             public boolean shouldOverrideUrlLoading(WebView view, String url) {
123                 mainWebView.loadUrl(url);
124                 return true;
125             }
126
127             /* These errors do not provide any useful information and clutter the screen.
128             public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
129                 Toast.makeText(mainWebViewActivity, "Error loading " + request + "   Error: " + error, Toast.LENGTH_SHORT).show();
130             }
131             */
132
133             // Update the URL in urlTextBox when the page starts to load.
134             @Override
135             public void onPageStarted(WebView view, String url, Bitmap favicon) {
136                 if (actionBar != null) {
137                     EditText urlTextBox = (EditText) actionBar.getCustomView().findViewById(R.id.urlTextBox);
138                     urlTextBox.setText(url);
139                 }
140             }
141
142             // Update formattedUrlString and urlTextBox.  It is necessary to do this after the page finishes loading because the final URL can change during load.
143             @Override
144             public void onPageFinished(WebView view, String url) {
145                 formattedUrlString = url;
146
147                 if (actionBar != null) {
148                     EditText urlTextBox = (EditText) actionBar.getCustomView().findViewById(R.id.urlTextBox);
149                     urlTextBox.setText(formattedUrlString);
150                 }
151             }
152         });
153
154         mainWebView.setWebChromeClient(new WebChromeClient() {
155             // Update the progress bar when a page is loading.
156             @Override
157             public void onProgressChanged(WebView view, int progress) {
158                 // Make sure that actionBar is not null.
159                 if (actionBar != null) {
160                     ProgressBar progressBar = (ProgressBar) actionBar.getCustomView().findViewById(R.id.progressBar);
161                     progressBar.setProgress(progress);
162                     if (progress < 100) {
163                         progressBar.setVisibility(View.VISIBLE);
164                     } else {
165                         progressBar.setVisibility(View.GONE);
166                     }
167                 }
168             }
169
170             // Set the favorite icon when it changes.
171             @Override
172             public void onReceivedIcon(WebView view, Bitmap icon) {
173                 // Save a copy of the favorite icon for use if a shortcut is added to the home screen.
174                 favoriteIcon = icon;
175
176                 // Place the favorite icon in the actionBar if it is not null.
177                 if (actionBar != null) {
178                     ImageView imageViewFavoriteIcon = (ImageView) actionBar.getCustomView().findViewById(R.id.favoriteIcon);
179                     imageViewFavoriteIcon.setImageBitmap(Bitmap.createScaledBitmap(icon, 64, 64, true));
180                 }
181             }
182
183             // Enter full screen video
184             @Override
185             public void onShowCustomView(View view, CustomViewCallback callback) {
186                 if (getSupportActionBar() != null) {
187                     getSupportActionBar().hide();
188                 }
189
190                 fullScreenVideoFrameLayout.addView(view);
191                 fullScreenVideoFrameLayout.setVisibility(View.VISIBLE);
192
193                 mainWebView.setVisibility(View.GONE);
194
195                 /* SYSTEM_UI_FLAG_HIDE_NAVIGATION hides the navigation bars on the bottom or right of the screen.
196                 ** SYSTEM_UI_FLAG_FULLSCREEN hides the status bar across the top of the screen.
197                 ** SYSTEM_UI_FLAG_IMMERSIVE_STICKY makes the navigation and status bars ghosted overlays and automatically rehides them.
198                 */
199
200                 // Set the one flag supported by API >= 14.
201                 if (Build.VERSION.SDK_INT >= 14) {
202                     view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
203                 }
204
205                 // Set the two flags that are supported by API >= 16.
206                 if (Build.VERSION.SDK_INT >= 16) {
207                     view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
208                 }
209
210                 // Set all three flags that are supported by API >= 19.
211                 if (Build.VERSION.SDK_INT >= 19) {
212                     view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
213                 }
214             }
215
216             // Exit full screen video
217             public void onHideCustomView() {
218                 if (getSupportActionBar() != null) {
219                     getSupportActionBar().show();
220                 }
221
222                 mainWebView.setVisibility(View.VISIBLE);
223
224                 fullScreenVideoFrameLayout.removeAllViews();
225                 fullScreenVideoFrameLayout.setVisibility(View.GONE);
226             }
227         });
228
229         // Allow the downloading of files.
230         mainWebView.setDownloadListener(new DownloadListener() {
231             // Launch the Android download manager when a link leads to a download.
232             @Override
233             public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
234                 DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
235                 DownloadManager.Request requestUri = new DownloadManager.Request(Uri.parse(url));
236
237                 // Add the URL as the description for the download.
238                 requestUri.setDescription(url);
239
240                 // Show the download notification after the download is completed if the API is 11 or greater.
241                 if (Build.VERSION.SDK_INT >= 11) {
242                     requestUri.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
243                 }
244
245                 downloadManager.enqueue(requestUri);
246                 Toast.makeText(mainWebViewActivity, "Download started", Toast.LENGTH_SHORT).show();
247             }
248         });
249
250         // Allow pinch to zoom.
251         mainWebView.getSettings().setBuiltInZoomControls(true);
252
253         // Hide zoom controls if the API is 11 or greater.
254         if (Build.VERSION.SDK_INT >= 11) {
255             mainWebView.getSettings().setDisplayZoomControls(false);
256         }
257
258         // Set JavaScript initial status.
259         enableJavaScript = true;
260         mainWebView.getSettings().setJavaScriptEnabled(enableJavaScript);
261
262         // Set DOM Storage initial status.
263         enableDomStorage = true;
264         mainWebView.getSettings().setDomStorageEnabled(enableDomStorage);
265
266         // Get the intent information that started the app.
267         final Intent intent = getIntent();
268
269         if (intent.getData() != null) {
270             // Get the intent data and convert it to a string.
271             final Uri intentUriData = intent.getData();
272             formattedUrlString = intentUriData.toString();
273         }
274
275         // If formattedUrlString is null assign the homepage to it.
276         if (formattedUrlString == null) {
277             formattedUrlString = homepage;
278         }
279
280         // Load the initial website.
281         mainWebView.loadUrl(formattedUrlString);
282     }
283
284     @Override
285     public boolean onCreateOptionsMenu(Menu menu) {
286         // Inflate the menu; this adds items to the action bar if it is present.
287         getMenuInflater().inflate(R.menu.menu_webview, menu);
288         MenuItem toggleJavaScriptMenuItem = menu.findItem(R.id.toggleJavaScript);
289         MenuItem toggleDomStorageMenuItem = menu.findItem(R.id.toggleDomStorage);
290
291         // Set the JavaScript menu item checkbox initial status.
292         toggleJavaScriptMenuItem.setChecked(enableJavaScript);
293
294         // Set the DOM Storage menu item checkbox initial status.
295         toggleDomStorageMenuItem.setChecked(enableDomStorage);
296
297         return true;
298     }
299
300     @Override
301     // @TargetApi(11) turns off the errors regarding copy and paste, which are removed from view in menu_webview.xml for lower version of Android.
302     @TargetApi(11)
303     // Remove Android Studio's warning about the dangers of using SetJavaScriptEnabled.
304     @SuppressLint("SetJavaScriptEnabled")
305     public boolean onOptionsItemSelected(MenuItem menuItem) {
306         int menuItemId = menuItem.getItemId();
307         ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
308
309         // Sets the commands that relate to the menu entries.
310         switch (menuItemId) {
311             case R.id.toggleJavaScript:
312                 if (enableJavaScript) {
313                     enableJavaScript = false;
314                     menuItem.setChecked(false);
315                     mainWebView.getSettings().setJavaScriptEnabled(false);
316                     mainWebView.loadUrl(formattedUrlString);
317                 } else {
318                     enableJavaScript = true;
319                     menuItem.setChecked(true);
320                     mainWebView.getSettings().setJavaScriptEnabled(true);
321                     mainWebView.loadUrl(formattedUrlString);
322                 }
323                 return true;
324
325             case R.id.toggleDomStorage:
326                 if (enableDomStorage) {
327                     enableDomStorage = false;
328                     menuItem.setChecked(false);
329                     mainWebView.getSettings().setDomStorageEnabled(false);
330                     mainWebView.loadUrl(formattedUrlString);
331                 } else {
332                     enableDomStorage = true;
333                     menuItem.setChecked(true);
334                     mainWebView.getSettings().setDomStorageEnabled(true);
335                     mainWebView.loadUrl(formattedUrlString);
336                 }
337                 return true;
338
339             case R.id.home:
340                 mainWebView.loadUrl(homepage);
341                 return true;
342
343             case R.id.refresh:
344                 mainWebView.loadUrl(formattedUrlString);
345                 return true;
346
347             case R.id.back:
348                 mainWebView.goBack();
349                 return true;
350
351             case R.id.forward:
352                 mainWebView.goForward();
353                 return true;
354
355             case R.id.copyURL:
356                 // Make sure that actionBar is not null.
357                 if (actionBar != null) {
358                     EditText urlTextBox = (EditText) actionBar.getCustomView().findViewById(R.id.urlTextBox);
359                     clipboard.setPrimaryClip(ClipData.newPlainText("URL", urlTextBox.getText()));
360                 }
361                 return true;
362
363             case R.id.pasteURL:
364                 // Make sure that actionBar is not null.
365                 if (actionBar != null) {
366                     ClipData.Item clipboardData = clipboard.getPrimaryClip().getItemAt(0);
367                     EditText urlTextBox = (EditText) actionBar.getCustomView().findViewById(R.id.urlTextBox);
368                     urlTextBox.setText(clipboardData.coerceToText(this));
369                     try {
370                         loadUrlFromTextBox();
371                     } catch (UnsupportedEncodingException e) {
372                         e.printStackTrace();
373                     }
374                 }
375                 return true;
376
377             case R.id.shareURL:
378                 // Make sure that actionBar is not null.
379                 if (actionBar != null) {
380                     EditText urlTextBox = (EditText) actionBar.getCustomView().findViewById(R.id.urlTextBox);
381                     Intent shareIntent = new Intent();
382                     shareIntent.setAction(Intent.ACTION_SEND);
383                     shareIntent.putExtra(Intent.EXTRA_TEXT, urlTextBox.getText().toString());
384                     shareIntent.setType("text/plain");
385                     startActivity(Intent.createChooser(shareIntent, "Share URL"));
386                 }
387                 return true;
388
389             case R.id.addToHomescreen:
390                 // Show the CreateHomeScreenShortcut AlertDialog and name this instance createShortcut.
391                 AppCompatDialogFragment shortcutDialog = new CreateHomeScreenShortcut();
392                 shortcutDialog.show(getSupportFragmentManager(), "createShortcut");
393
394                 //Everything else will be handled by CreateHomeScreenShortcut and the associated listeners below.
395                 return true;
396
397             case R.id.downloads:
398                 // Launch the system Download Manager.
399                 Intent downloadManangerIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
400
401                 // Launch as a new task so that Download Manager and Privacy Browser show as separate windows in the recent tasks list.
402                 downloadManangerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
403
404                 startActivity(downloadManangerIntent);
405                 return true;
406
407             case R.id.about:
408                 // Show the AboutDialog AlertDialog and name this instance aboutDialog.
409                 AppCompatDialogFragment aboutDialog = new AboutDialog();
410                 aboutDialog.show(getSupportFragmentManager(), "aboutDialog");
411                 return true;
412
413             default:
414                 return super.onOptionsItemSelected(menuItem);
415         }
416     }
417
418     @Override
419     public void onCreateHomeScreenShortcutCancel(DialogFragment dialog) {
420         // Do nothing because the user selected "Cancel".
421     }
422
423     @Override
424     public void onCreateHomeScreenShortcutCreate(DialogFragment dialog) {
425         // Get shortcutNameEditText from the alert dialog.
426         EditText shortcutNameEditText = (EditText) dialog.getDialog().findViewById(R.id.shortcutNameEditText);
427
428         // Create the bookmark shortcut based on formattedUrlString.
429         Intent bookmarkShortcut = new Intent();
430         bookmarkShortcut.setAction(Intent.ACTION_VIEW);
431         bookmarkShortcut.setData(Uri.parse(formattedUrlString));
432
433         // Place the bookmark shortcut on the home screen.
434         Intent placeBookmarkShortcut = new Intent();
435         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.INTENT", bookmarkShortcut);
436         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.NAME", shortcutNameEditText.getText().toString());
437         placeBookmarkShortcut.putExtra("android.intent.extra.shortcut.ICON", favoriteIcon);
438         placeBookmarkShortcut.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
439         sendBroadcast(placeBookmarkShortcut);
440     }
441
442     // Override onBackPressed so that if mainWebView can go back it does when the system back button is pressed.
443     @Override
444     public void onBackPressed() {
445         final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
446
447         if (mainWebView.canGoBack()) {
448             mainWebView.goBack();
449         } else {
450             super.onBackPressed();
451         }
452     }
453
454     public void loadUrlFromTextBox() throws UnsupportedEncodingException {
455         // Make sure that actionBar is not null.
456         ActionBar actionBar = getSupportActionBar();
457         if (actionBar != null) {
458             final WebView mainWebView = (WebView) findViewById(R.id.mainWebView);
459             EditText urlTextBox = (EditText) actionBar.getCustomView().findViewById(R.id.urlTextBox);
460
461             // Get the text from urlTextInput and convert it to a string.
462             String unformattedUrlString = urlTextBox.getText().toString();
463             URL unformattedUrl = null;
464             Uri.Builder formattedUri = new Uri.Builder();
465
466             // Check to see if unformattedUrlString is a valid URL.  Otherwise, convert it into a Duck Duck Go search.
467             if (Patterns.WEB_URL.matcher(unformattedUrlString).matches()) {
468
469                 // Add http:// at the beginning if it is missing.  Otherwise the app will segfault.
470                 if (!unformattedUrlString.startsWith("http")) {
471                     unformattedUrlString = "http://" + unformattedUrlString;
472                 }
473
474                 // 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.
475                 try {
476                     unformattedUrl = new URL(unformattedUrlString);
477                 } catch (MalformedURLException e) {
478                     e.printStackTrace();
479                 }
480
481                 // The ternary operator (? :) makes sure that a null pointer exception is not thrown, which would happen if .get was called on a null value.
482                 final String scheme = unformattedUrl != null ? unformattedUrl.getProtocol() : null;
483                 final String authority = unformattedUrl != null ? unformattedUrl.getAuthority() : null;
484                 final String path = unformattedUrl != null ? unformattedUrl.getPath() : null;
485                 final String query = unformattedUrl != null ? unformattedUrl.getQuery() : null;
486                 final String fragment = unformattedUrl != null ? unformattedUrl.getRef() : null;
487
488                 formattedUri.scheme(scheme).authority(authority).path(path).query(query).fragment(fragment);
489                 formattedUrlString = formattedUri.build().toString();
490
491             } else {
492                 // Sanitize the search input and convert it to a DuckDuckGo search.
493                 final String encodedUrlString = URLEncoder.encode(unformattedUrlString, "UTF-8");
494                 formattedUrlString = "https://duckduckgo.com/?q=" + encodedUrlString;
495             }
496
497             mainWebView.loadUrl(formattedUrlString);
498
499             // Hides the keyboard so we can see the webpage.
500             InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
501             inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0);
502         }
503     }
504 }