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