X-Git-Url: https://gitweb.stoutner.com/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2FMainWebViewActivity.java;h=69ac5441e4831e920c61ba67aa27b8630e0616fd;hb=a3f354a396a97e465412dad957b2cd4b757efeb6;hp=982ff2d254c1de5ad9df6e43a2e7a489d22c970d;hpb=e91d2b50ba556a2873465977a9fce7a3c88bcbc3;p=PrivacyBrowserAndroid.git diff --git a/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java index 982ff2d2..69ac5441 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/MainWebViewActivity.java @@ -20,7 +20,6 @@ package com.stoutner.privacybrowser; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.DialogFragment; import android.app.DownloadManager; import android.content.Context; @@ -51,7 +50,10 @@ import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatDialogFragment; import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Patterns; +import android.view.ContextMenu; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -68,7 +70,10 @@ import android.webkit.WebViewDatabase; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; @@ -79,10 +84,10 @@ import java.util.Map; // We need to use AppCompatActivity from android.support.v7.app.AppCompatActivity to have access to the SupportActionBar until the minimum API is >= 21. public class MainWebViewActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, CreateHomeScreenShortcut.CreateHomeScreenSchortcutListener, - SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener { + SslCertificateError.SslCertificateErrorListener, DownloadFile.DownloadFileListener, DownloadImage.DownloadImageListener { // `appBar` is public static so it can be accessed from `OrbotProxyHelper`. - // It is also used in `onCreate()` and `onOptionsItemSelected()`. + // It is also used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. public static ActionBar appBar; // `favoriteIcon` is public static so it can be accessed from `CreateHomeScreenShortcut`, `BookmarksActivity`, `CreateBookmark`, `CreateBookmarkFolder`, and `EditBookmark`. @@ -97,7 +102,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation public static SslCertificate sslCertificate; - // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, and `loadUrlFromTextBox()`. + // 'mainWebView' is used in `onCreate()`, `onOptionsItemSelected()`, `onNavigationItemSelected()`, `onRestart()`, `onCreateContextMenu()`, `findPreviousOnPage()`, `findNextOnPage()`, `closeFindOnPage()`, and `loadUrlFromTextBox()`. private WebView mainWebView; // `swipeRefreshLayout` is used in `onCreate()`, `onPrepareOptionsMenu`, and `onRestart()`. @@ -106,7 +111,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `cookieManager` is used in `onCreate()`, `onOptionsItemSelected()`, and `onNavigationItemSelected()`, and `onRestart()`. private CookieManager cookieManager; - // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, and `loadUrlFromTextBox()`. + // `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrlFromTextBox()`. private final Map customHeaders = new HashMap<>(); // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applySettings()`. @@ -155,7 +160,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // `sslErrorHandler` is used in `onCreate()`, `onSslErrorCancel()`, and `onSslErrorProceed`. private SslErrorHandler sslErrorHandler; - private MenuItem toggleJavaScript; + // `findOnPageEditText` is used in `onCreate()`, `onOptionsItemSelected()`, and `closeFindOnPage()`. + private EditText findOnPageEditText; + + // `inputMethodManager` is used in `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `closeFindOnPage()`. + private InputMethodManager inputMethodManager; @Override // 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. @@ -164,6 +173,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation super.onCreate(savedInstanceState); setContentView(R.layout.main_coordinatorlayout); + // Get a handle for `inputMethodManager`. + inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + // We need to use the SupportActionBar from android.support.v7.app.ActionBar until the minimum API is >= 21. Toolbar supportAppBar = (Toolbar) findViewById(R.id.appBar); setSupportActionBar(supportAppBar); @@ -179,8 +191,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Set the "go" button on the keyboard to load the URL in urlTextBox. urlTextBox = (EditText) appBar.getCustomView().findViewById(R.id.urlTextBox); urlTextBox.setOnKeyListener(new View.OnKeyListener() { + @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - // If the event is a key-down event on the "enter" button, load the URL. + // If the event is a key-down event on the `enter` button, load the URL. if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // Load the URL into the mainWebView and consume the event. try { @@ -197,7 +210,66 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } }); + // Get handles for `fullScreenVideoFrameLayout`, `mainWebView`, and `find_on_page_edittext`. final FrameLayout fullScreenVideoFrameLayout = (FrameLayout) findViewById(R.id.fullScreenVideoFrameLayout); + mainWebView = (WebView) findViewById(R.id.mainWebView); + findOnPageEditText = (EditText) findViewById(R.id.find_on_page_edittext); + + // Update `findOnPageCountTextView`. + mainWebView.setFindListener(new WebView.FindListener() { + // Get a handle for `findOnPageCountTextView`. + TextView findOnPageCountTextView = (TextView) findViewById(R.id.find_on_page_count_textview); + + @Override + public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) { + if ((isDoneCounting) && (numberOfMatches == 0)) { // There are no matches. + // Set `findOnPageCountTextView` to `0/0`. + findOnPageCountTextView.setText(R.string.zero_of_zero); + } else if (isDoneCounting) { // There are matches. + // `activeMatchOrdinal` is zero-based. + int activeMatch = activeMatchOrdinal + 1; + + // Set `findOnPageCountTextView`. + findOnPageCountTextView.setText(activeMatch + "/" + numberOfMatches); + } + } + }); + + // Search for the string on the page whenever a character changes in the `findOnPageEditText`. + findOnPageEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing. + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // Do nothing. + } + + @Override + public void afterTextChanged(Editable s) { + // Search for the text in `mainWebView`. + mainWebView.findAllAsync(findOnPageEditText.getText().toString()); + } + }); + + // Set the `check mark` button for the `findOnPageEditText` keyboard to close the soft keyboard. + findOnPageEditText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) { // The `enter` key was pressed. + // Hide the soft keyboard. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + + // Consume the event. + return true; + } else { // A different key was pressed. + // Do not consume the event. + return false; + } + } + }); // Implement swipe to refresh swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout); @@ -209,17 +281,45 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } }); - mainWebView = (WebView) findViewById(R.id.mainWebView); - // Create the navigation drawer. drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout); - // The DrawerTitle identifies the drawer in accessibility mode. + // `DrawerTitle` identifies the drawer in accessibility mode. drawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.navigation_drawer)); // Listen for touches on the navigation menu. final NavigationView navigationView = (NavigationView) findViewById(R.id.navigationView); navigationView.setNavigationItemSelectedListener(this); + // Get handles for `navigationMenu` and the back and forward menu items. The menu is zero-based, so item 1 and 2 and the second and third items in the menu. + final Menu navigationMenu = navigationView.getMenu(); + final MenuItem navigationBackMenuItem = navigationMenu.getItem(1); + final MenuItem navigationForwardMenuItem = navigationMenu.getItem(2); + + // The `DrawerListener` allows us to update the Navigation Menu. + drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() { + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + } + + @Override + public void onDrawerOpened(View drawerView) { + } + + @Override + public void onDrawerClosed(View drawerView) { + } + + @Override + public void onDrawerStateChanged(int newState) { + // Update the back and forward menu items every time the drawer opens. + navigationBackMenuItem.setEnabled(mainWebView.canGoBack()); + navigationForwardMenuItem.setEnabled(mainWebView.canGoForward()); + + // Hide the keyboard so we can see the navigation menu. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + } + }); + // drawerToggle creates the hamburger icon at the start of the AppBar. drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, supportAppBar, R.string.open_navigation, R.string.close_navigation); @@ -280,8 +380,8 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation sslErrorHandler = handler; // Display the SSL error `AlertDialog`. - DialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error); - sslCertificateErrorDialogFragment.show(getFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); + AppCompatDialogFragment sslCertificateErrorDialogFragment = SslCertificateError.displayDialog(error); + sslCertificateErrorDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.ssl_certificate_error)); } }); @@ -350,6 +450,9 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation } }); + // Register `mainWebView` for a context menu. This is used to see link targets and download images. + registerForContextMenu(mainWebView); + // Allow the downloading of files. mainWebView.setDownloadListener(new DownloadListener() { @Override @@ -444,7 +547,6 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation updatePrivacyIcons(false); // Get handles for the menu items. - toggleJavaScript = menu.findItem(R.id.toggleJavaScript); MenuItem toggleFirstPartyCookies = menu.findItem(R.id.toggleFirstPartyCookies); MenuItem toggleThirdPartyCookies = menu.findItem(R.id.toggleThirdPartyCookies); MenuItem toggleDomStorage = menu.findItem(R.id.toggleDomStorage); @@ -742,13 +844,30 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.getSettings().setTextZoom(200); return true; - /* case R.id.find_on_page: - appBar.setCustomView(R.layout.find_on_page_app_bar); - toggleJavaScript.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); - appBar.invalidateOptionsMenu(); + // Hide the URL app bar. + Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar); + appBarToolbar.setVisibility(View.GONE); + + // Show the Find on Page `RelativeLayout`. + LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); + findOnPageLinearLayout.setVisibility(View.VISIBLE); + + // Display the keyboard. We have to wait 200 ms before running the command to work around a bug in Android. + // http://stackoverflow.com/questions/5520085/android-show-softkeyboard-with-showsoftinput-is-not-working + findOnPageEditText.postDelayed(new Runnable() + { + @Override + public void run() + { + // Set the focus on `findOnPageEditText`. + findOnPageEditText.requestFocus(); + + // Display the keyboard. + inputMethodManager.showSoftInput(findOnPageEditText, 0); + } + }, 200); return true; - */ case R.id.share: Intent shareIntent = new Intent(); @@ -875,15 +994,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Clear `customHeaders`. customHeaders.clear(); - // Destroy the internal state of the webview. + // Detach all views from `mainWebViewRelativeLayout`. + RelativeLayout mainWebViewRelativeLayout = (RelativeLayout) findViewById(R.id.mainWebViewRelativeLayout); + mainWebViewRelativeLayout.removeAllViews(); + + // Destroy the internal state of `mainWebView`. mainWebView.destroy(); - // Close Privacy Browser. finishAndRemoveTask also removes Privacy Browser from the recent app list. + // Close Privacy Browser. `finishAndRemoveTask` also removes Privacy Browser from the recent app list. if (Build.VERSION.SDK_INT >= 21) { finishAndRemoveTask(); } else { finish(); } + + // Remove the terminated program from RAM. The status code is `0`. + System.exit(0); break; default: @@ -917,6 +1043,134 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // ActivityCompat.invalidateOptionsMenu(this); } + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) { + // Store the `HitTestResult`. + final WebView.HitTestResult hitTestResult = mainWebView.getHitTestResult(); + + // Create strings. + final String imageUrl; + final String linkUrl; + + switch (hitTestResult.getType()) { + // `SRC_ANCHOR_TYPE` is a link. + case WebView.HitTestResult.SRC_ANCHOR_TYPE: + // Get the target URL. + linkUrl = hitTestResult.getExtra(); + + // Set the target URL as the title of the `ContextMenu`. + menu.setHeaderTitle(linkUrl); + + // Add a `Load URL` button. + menu.add(R.string.load_url).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + mainWebView.loadUrl(linkUrl, customHeaders); + return false; + } + }); + + // Add a `Cancel` button, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + + case WebView.HitTestResult.EMAIL_TYPE: + // Get the target URL. + linkUrl = hitTestResult.getExtra(); + + // Set the target URL as the title of the `ContextMenu`. + menu.setHeaderTitle(linkUrl); + + // Add a `Write Email` button. + menu.add(R.string.write_email).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // We use `ACTION_SENDTO` instead of `ACTION_SEND` so that only email programs are launched. + Intent emailIntent = new Intent(Intent.ACTION_SENDTO); + + // Parse the url and set it as the data for the `Intent`. + emailIntent.setData(Uri.parse("mailto:" + linkUrl)); + + // `FLAG_ACTIVITY_NEW_TASK` opens the email program in a new task instead as part of Privacy Browser. + emailIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + // Make it so. + startActivity(emailIntent); + return false; + } + }); + + // Add a `Cancel` button, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + + // `IMAGE_TYPE` is an image. + case WebView.HitTestResult.IMAGE_TYPE: + // Get the image URL. + imageUrl = hitTestResult.getExtra(); + + // Set the image URL as the title of the `ContextMenu`. + menu.setHeaderTitle(imageUrl); + + // Add a `View Image` button. + menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + mainWebView.loadUrl(imageUrl, customHeaders); + return false; + } + }); + + // Add a `Download Image` button. + menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl); + downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + return false; + } + }); + + // Add a `Cancel` button, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + + + // `SRC_IMAGE_ANCHOR_TYPE` is an image that is also a link. + case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: + // Get the image URL. + imageUrl = hitTestResult.getExtra(); + + // Set the image URL as the title of the `ContextMenu`. + menu.setHeaderTitle(imageUrl); + + // Add a `View Image` button. + menu.add(R.string.view_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + mainWebView.loadUrl(imageUrl, customHeaders); + return false; + } + }); + + // Add a `Download Image` button. + menu.add(R.string.download_image).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + // Show the `DownloadImage` `AlertDialog` and name this instance `@string/download`. + AppCompatDialogFragment downloadImageDialogFragment = DownloadImage.imageUrl(imageUrl); + downloadImageDialogFragment.show(getSupportFragmentManager(), getResources().getString(R.string.download)); + return false; + } + }); + + // Add a `Cancel` button, which by default closes the `ContextMenu`. + menu.add(R.string.cancel); + break; + } + } + @Override public void onCreateHomeScreenShortcut(AppCompatDialogFragment dialogFragment) { // Get shortcutNameEditText from the alert dialog. @@ -936,9 +1190,44 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation sendBroadcast(placeBookmarkShortcut); } + @Override + public void onDownloadImage(AppCompatDialogFragment dialogFragment, String imageUrl) { + // Get a handle for the system `DOWNLOAD_SERVICE`. + DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); + + // Parse `imageUrl`. + DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(imageUrl)); + + // Get the file name from `dialogFragment`. + EditText downloadImageNameEditText = (EditText) dialogFragment.getDialog().findViewById(R.id.download_image_name); + String imageName = downloadImageNameEditText.getText().toString(); + + // Once we have `WRITE_EXTERNAL_STORAGE` permissions we can use `setDestinationInExternalPublicDir`. + if (Build.VERSION.SDK_INT >= 23) { // If API >= 23, set the download save in the the `DIRECTORY_DOWNLOADS` using `imageName`. + downloadRequest.setDestinationInExternalFilesDir(this, "/", imageName); + } else { // Only set the title using `imageName`. + downloadRequest.setTitle(imageName); + } + + // Allow `MediaScanner` to index the download if it is a media file. + downloadRequest.allowScanningByMediaScanner(); + + // Add the URL as the description for the download. + downloadRequest.setDescription(imageUrl); + + // Show the download notification after the download is completed. + downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + + // Initiate the download. + downloadManager.enqueue(downloadRequest); + } + @Override public void onDownloadFile(AppCompatDialogFragment dialogFragment, String downloadUrl) { + // Get a handle for the system `DOWNLOAD_SERVICE`. DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); + + // Parse `downloadUrl`. DownloadManager.Request downloadRequest = new DownloadManager.Request(Uri.parse(downloadUrl)); // Get the file name from `dialogFragment`. @@ -961,7 +1250,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation // Show the download notification after the download is completed. downloadRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); - // Initiate the download and display a Snackbar. + // Initiate the download. downloadManager.enqueue(downloadRequest); } @@ -1072,8 +1361,36 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation mainWebView.loadUrl(formattedUrlString, customHeaders); - // Hides the keyboard so we can see the webpage. - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE); + // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. + inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); + } + + public void findPreviousOnPage(View view) { + // Go to the previous highlighted phrase on the page. `false` goes backwards instead of forwards. + mainWebView.findNext(false); + } + + public void findNextOnPage(View view) { + // Go to the next highlighted phrase on the page. `true` goes forwards instead of backwards. + mainWebView.findNext(true); + } + + public void closeFindOnPage(View view) { + // Delete the contents of `find_on_page_edittext`. + findOnPageEditText.setText(null); + + // Clear the highlighted phrases. + mainWebView.clearMatches(); + + // Hide the Find on Page `RelativeLayout`. + LinearLayout findOnPageLinearLayout = (LinearLayout) findViewById(R.id.find_on_page_linearlayout); + findOnPageLinearLayout.setVisibility(View.GONE); + + // Show the URL app bar. + Toolbar appBarToolbar = (Toolbar) findViewById(R.id.appBar); + appBarToolbar.setVisibility(View.VISIBLE); + + // Hide the keyboard so we can see the webpage. `0` indicates no additional flags. inputMethodManager.hideSoftInputFromWindow(mainWebView.getWindowToken(), 0); }