diff --git a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java index 191560c..c8652b4 100644 --- a/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java +++ b/app/src/main/java/acr/browser/lightning/download/DownloadHandler.java @@ -13,12 +13,17 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Environment; +import android.support.annotation.NonNull; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.util.Log; import android.webkit.CookieManager; import android.webkit.URLUtil; +import java.io.File; +import java.io.IOException; +import java.net.URI; + import acr.browser.lightning.R; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.Utils; @@ -30,6 +35,10 @@ public class DownloadHandler { private static final String LOGTAG = "DLHandler"; + public static final String DEFAULT_DOWNLOAD_PATH = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + .getPath(); + /** * Notify the host application a download should be done, or that the data @@ -153,6 +162,7 @@ public class DownloadHandler { // This only happens for very bad urls, we want to catch the // exception here Log.e(LOGTAG, "Exception while trying to parse url '" + url + '\'', e); + Utils.showSnackbar(activity, R.string.problem_download); return; } @@ -171,9 +181,29 @@ public class DownloadHandler { // depending on mimetype? String location = PreferenceManager.getInstance().getDownloadDirectory(); - request.setDestinationInExternalPublicDir(location, filename); + Uri downloadLocation; + if (location != null) { + downloadLocation = Uri.parse(addNecessarySlashes(location)); + } else { + downloadLocation = Uri.parse(addNecessarySlashes(DEFAULT_DOWNLOAD_PATH)); + PreferenceManager.getInstance().setDownloadDirectory(downloadLocation.getPath()); + } + + File dir = new File(downloadLocation.getPath()); + if (!dir.isDirectory() && !dir.mkdirs()) { + // Cannot make the directory + Utils.showSnackbar(activity, R.string.problem_location_download); + return; + } + + if (!isWriteAccessAvailable(downloadLocation)) { + Utils.showSnackbar(activity, R.string.problem_location_download); + return; + } + request.setDestinationUri(downloadLocation); // let this downloaded file be scanned by MediaScanner - so that it can // show up in Gallery app, for example. + request.setVisibleInDownloadsUi(true); request.allowScanningByMediaScanner(); request.setDescription(webAddress.getHost()); // XXX: Have to use the old url since the cookies were stored using the @@ -191,7 +221,7 @@ public class DownloadHandler { } else { final DownloadManager manager = (DownloadManager) activity .getSystemService(Context.DOWNLOAD_SERVICE); - new Thread("Browser download") { + new Thread() { @Override public void run() { try { @@ -203,8 +233,100 @@ public class DownloadHandler { } } }.start(); - Utils.showSnackbar(activity, R.string.download_pending); + Utils.showSnackbar(activity, activity.getString(R.string.download_pending) + ' ' + filename); + } + + } + + private static final String sFileName = "test"; + private static final String sFileExtension = ".txt"; + + /** + * Determine whether there is write access in the given directory. Returns false if a + * file cannot be created in the directory or if the directory does not exist. + * + * @param directory the directory to check for write access + * @return returns true if the directory can be written to or is in a directory that can + * be written to. false if there is no write access. + */ + public static boolean isWriteAccessAvailable(String directory) { + if (directory == null || directory.isEmpty()) { + return false; + } + String dir = addNecessarySlashes(directory); + dir = getFirstRealParentDirectory(dir); + File file = new File(dir + sFileName + sFileExtension); + for (int n = 0; n < 100; n++) { + if (!file.exists()) { + try { + if (file.createNewFile()) { + file.delete(); + } + return true; + } catch (IOException ignored) { + return false; + } + } else { + file = new File(dir + sFileName + '-' + n + sFileExtension); + } + } + return file.canWrite(); + } + + /** + * Returns the first parent directory of a directory that exists. This is useful + * for subdirectories that do not exist but their parents do. + * + * @param directory the directory to find the first existent parent + * @return the first existent parent + */ + private static String getFirstRealParentDirectory(String directory) { + if (directory == null || directory.isEmpty()) { + return "/"; + } + directory = addNecessarySlashes(directory); + File file = new File(directory); + if (!file.isDirectory()) { + int indexSlash = directory.lastIndexOf('/'); + if (indexSlash > 0) { + String parent = directory.substring(0, indexSlash); + int previousIndex = parent.lastIndexOf('/'); + if (previousIndex > 0) { + return getFirstRealParentDirectory(parent.substring(0, previousIndex)); + } else { + return "/"; + } + } else { + return "/"; + } + } else { + return directory; } + } + private static boolean isWriteAccessAvailable(Uri fileUri) { + File file = new File(fileUri.getPath()); + try { + if (file.createNewFile()) { + file.delete(); + } + return true; + } catch (IOException ignored) { + return false; + } } + + public static String addNecessarySlashes(String originalPath) { + if (originalPath == null || originalPath.length() == 0) { + return "/"; + } + if (originalPath.charAt(originalPath.length() - 1) != '/') { + originalPath = originalPath + '/'; + } + if (originalPath.charAt(0) != '/') { + originalPath = '/' + originalPath; + } + return originalPath; + } + } diff --git a/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java b/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java index 14269a8..af914c7 100644 --- a/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java +++ b/app/src/main/java/acr/browser/lightning/download/FetchUrlMimeType.java @@ -27,7 +27,7 @@ import acr.browser.lightning.utils.Utils; */ public class FetchUrlMimeType extends Thread { - private final Context mContext; + private final Activity mActivity; private final DownloadManager.Request mRequest; @@ -39,12 +39,11 @@ public class FetchUrlMimeType extends Thread { public FetchUrlMimeType(Activity activity, DownloadManager.Request request, String uri, String cookies, String userAgent) { - mContext = activity.getApplicationContext(); + mActivity = activity; mRequest = request; mUri = uri; mCookies = cookies; mUserAgent = userAgent; - Utils.showSnackbar(activity, R.string.download_pending); } @Override @@ -87,6 +86,7 @@ public class FetchUrlMimeType extends Thread { connection.disconnect(); } + String filename = ""; if (mimeType != null) { if (mimeType.equalsIgnoreCase("text/plain") || mimeType.equalsIgnoreCase("application/octet-stream")) { @@ -96,13 +96,14 @@ public class FetchUrlMimeType extends Thread { mRequest.setMimeType(newMimeType); } } - String filename = URLUtil.guessFileName(mUri, contentDisposition, mimeType); + filename = URLUtil.guessFileName(mUri, contentDisposition, mimeType); mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); } // Start the download - DownloadManager manager = (DownloadManager) mContext + DownloadManager manager = (DownloadManager) mActivity .getSystemService(Context.DOWNLOAD_SERVICE); manager.enqueue(mRequest); + Utils.showSnackbar(mActivity, mActivity.getString(R.string.download_pending) + ' ' + filename); } } diff --git a/app/src/main/java/acr/browser/lightning/download/WebAddress.java b/app/src/main/java/acr/browser/lightning/download/WebAddress.java index 7aa1eda..d52edf7 100644 --- a/app/src/main/java/acr/browser/lightning/download/WebAddress.java +++ b/app/src/main/java/acr/browser/lightning/download/WebAddress.java @@ -3,6 +3,8 @@ */ package acr.browser.lightning.download; +import android.support.annotation.NonNull; + import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -11,13 +13,13 @@ import static android.util.Patterns.GOOD_IRI_CHAR; /** * Web Address Parser - * + *

* This is called WebAddress, rather than URL or URI, because it attempts to * parse the stuff that a user will actually type into a browser address widget. - * + *

* Unlike java.net.uri, this parser will not choke on URIs missing schemes. It * will only throw a ParseException if the input is really hosed. - * + *

* If given an https scheme but no port, fills in port */ public class WebAddress { @@ -43,7 +45,7 @@ public class WebAddress { /** * Parses given URI-like string. */ - public WebAddress(String address) { + public WebAddress(String address) throws IllegalArgumentException { if (address == null) { throw new IllegalArgumentException("address can't be null"); @@ -134,7 +136,7 @@ public class WebAddress { return mScheme; } - public void setHost(String host) { + public void setHost(@NonNull String host) { mHost = host; } diff --git a/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java index 6698e29..799f272 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/GeneralSettingsFragment.java @@ -5,27 +5,28 @@ package acr.browser.lightning.fragment; import android.app.Activity; import android.content.DialogInterface; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.PreferenceFragment; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; +import android.text.Editable; import android.text.InputFilter; +import android.text.TextWatcher; import android.util.Log; -import android.util.TypedValue; import android.view.View; +import android.view.ViewGroup; import android.widget.EditText; import android.widget.LinearLayout; -import android.widget.TextView; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.download.DownloadHandler; import acr.browser.lightning.preference.PreferenceManager; import acr.browser.lightning.utils.ProxyUtils; +import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.Utils; public class GeneralSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener { @@ -113,7 +114,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe setSearchEngineSummary(mPreferences.getSearchChoice()); - downloadloc.setSummary(Constants.EXTERNAL_STORAGE + '/' + mDownloadLocation); + downloadloc.setSummary(mDownloadLocation); if (mHomepage.contains("about:home")) { home.setSummary(getResources().getString(R.string.action_homepage)); @@ -143,7 +144,7 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe boolean imagesBool = mPreferences.getBlockImagesEnabled(); boolean enableJSBool = mPreferences.getJavaScriptEnabled(); - proxy.setEnabled(Constants.FULL_VERSION); +// proxy.setEnabled(Constants.FULL_VERSION); cbAds.setEnabled(Constants.FULL_VERSION); cbFlash.setEnabled(API < 19); @@ -386,22 +387,21 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe mDownloadLocation = mPreferences.getDownloadDirectory(); int n; if (mDownloadLocation.contains(Environment.DIRECTORY_DOWNLOADS)) { - n = 1; + n = 0; } else { - n = 2; + n = 1; } - picker.setSingleChoiceItems(R.array.download_folder, n - 1, + picker.setSingleChoiceItems(R.array.download_folder, n, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - switch (which + 1) { - case 1: - mPreferences.setDownloadDirectory(Environment.DIRECTORY_DOWNLOADS); - downloadloc.setSummary(Constants.EXTERNAL_STORAGE + '/' - + Environment.DIRECTORY_DOWNLOADS); + switch (which) { + case 0: + mPreferences.setDownloadDirectory(DownloadHandler.DEFAULT_DOWNLOAD_PATH); + downloadloc.setSummary(DownloadHandler.DEFAULT_DOWNLOAD_PATH); break; - case 2: + case 1: downPicker(); break; } @@ -479,36 +479,42 @@ public class GeneralSettingsFragment extends PreferenceFragment implements Prefe LinearLayout layout = new LinearLayout(mActivity); downLocationPicker.setTitle(getResources().getString(R.string.title_download_location)); final EditText getDownload = new EditText(mActivity); - getDownload.setText(mPreferences.getDownloadDirectory()); + getDownload.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + getDownload.setText(PreferenceManager.getInstance().getDownloadDirectory()); + final int errorColor = ContextCompat.getColor(getActivity(), R.color.error_red); + final int regularColor = ThemeUtils.getTextColor(getActivity()); + getDownload.setTextColor(regularColor); + getDownload.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } - int padding = Utils.dpToPx(10); + @Override + public void afterTextChanged(Editable s) { + if (!DownloadHandler.isWriteAccessAvailable(s.toString())) { + getDownload.setTextColor(errorColor); + } else { + getDownload.setTextColor(regularColor); + } + } + }); + getDownload.setText(mPreferences.getDownloadDirectory()); - TextView v = new TextView(mActivity); - v.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); - v.setTextColor(Color.DKGRAY); - v.setText(Constants.EXTERNAL_STORAGE + '/'); - v.setPadding(padding, padding, 0, padding); - layout.addView(v); layout.addView(getDownload); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - Drawable drawable; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - drawable = getResources().getDrawable(android.R.drawable.edit_text, getActivity().getTheme()); - } else { - drawable = getResources().getDrawable(android.R.drawable.edit_text); - } - layout.setBackground(drawable); - } else { - layout.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.edit_text)); - } downLocationPicker.setView(layout); downLocationPicker.setPositiveButton(getResources().getString(R.string.action_ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String text = getDownload.getText().toString(); + text = DownloadHandler.addNecessarySlashes(text); mPreferences.setDownloadDirectory(text); - downloadloc.setSummary(Constants.EXTERNAL_STORAGE + '/' + text); + downloadloc.setSummary(text); } }); downLocationPicker.show(); diff --git a/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java b/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java index 9e186b4..0fda30e 100644 --- a/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java +++ b/app/src/main/java/acr/browser/lightning/preference/PreferenceManager.java @@ -1,10 +1,10 @@ package acr.browser.lightning.preference; import android.content.SharedPreferences; -import android.os.Environment; import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.constant.Constants; +import acr.browser.lightning.download.DownloadHandler; public class PreferenceManager { @@ -14,7 +14,7 @@ public class PreferenceManager { public static final String BLOCK_IMAGES = "blockimages"; public static final String CLEAR_CACHE_EXIT = "cache"; public static final String COOKIES = "cookies"; - public static final String DOWNLOAD_DIRECTORY = "download"; + public static final String DOWNLOAD_DIRECTORY = "downloadLocation"; public static final String FULL_SCREEN = "fullscreen"; public static final String HIDE_STATUS_BAR = "hidestatus"; public static final String HOMEPAGE = "home"; @@ -117,7 +117,7 @@ public class PreferenceManager { } public String getDownloadDirectory() { - return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, Environment.DIRECTORY_DOWNLOADS); + return mPrefs.getString(Name.DOWNLOAD_DIRECTORY, DownloadHandler.DEFAULT_DOWNLOAD_PATH); } public int getFlashSupport() { @@ -244,7 +244,7 @@ public class PreferenceManager { return mPrefs.getString(Name.TEXT_ENCODING, Constants.DEFAULT_ENCODING); } - public boolean getShowTabsInDrawer(boolean defaultValue){ + public boolean getShowTabsInDrawer(boolean defaultValue) { return mPrefs.getBoolean(Name.SHOW_TABS_IN_DRAWER, defaultValue); } @@ -260,7 +260,7 @@ public class PreferenceManager { mPrefs.edit().putString(name, value).apply(); } - public void setShowTabsInDrawer(boolean show){ + public void setShowTabsInDrawer(boolean show) { putBoolean(Name.SHOW_TABS_IN_DRAWER, show); } diff --git a/app/src/main/java/acr/browser/lightning/utils/ThemeUtils.java b/app/src/main/java/acr/browser/lightning/utils/ThemeUtils.java index 29327fb..890fbb6 100644 --- a/app/src/main/java/acr/browser/lightning/utils/ThemeUtils.java +++ b/app/src/main/java/acr/browser/lightning/utils/ThemeUtils.java @@ -120,4 +120,8 @@ public class ThemeUtils { } return new ColorDrawable(color); } + + public static int getTextColor(Context context){ + return getColor(context, android.R.attr.editTextColor); + } } diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 2d7f07b..1e2e2cc 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -33,4 +33,6 @@ #8A000000 #FFFFFFFF + #F44336 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 88ded10..9183d6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -101,6 +101,8 @@ Find in Page Starting download\u2026 Can only download \"http\" or \"https\" URLs. + Invalid URL encountered, cannot download + Cannot download to the specified location No SD card USB storage is required to download the file. USB storage unavailable