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