|
|
@ -23,16 +23,26 @@ import android.webkit.CookieManager; |
|
|
|
import android.webkit.MimeTypeMap; |
|
|
|
import android.webkit.MimeTypeMap; |
|
|
|
import android.webkit.URLUtil; |
|
|
|
import android.webkit.URLUtil; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.anthonycr.bonsai.SingleOnSubscribe; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
import java.io.File; |
|
|
|
import java.io.IOException; |
|
|
|
import java.io.IOException; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import javax.inject.Inject; |
|
|
|
|
|
|
|
|
|
|
|
import acr.browser.lightning.BuildConfig; |
|
|
|
import acr.browser.lightning.BuildConfig; |
|
|
|
import acr.browser.lightning.R; |
|
|
|
import acr.browser.lightning.R; |
|
|
|
|
|
|
|
import acr.browser.lightning.activity.BrowserActivity; |
|
|
|
import acr.browser.lightning.activity.MainActivity; |
|
|
|
import acr.browser.lightning.activity.MainActivity; |
|
|
|
|
|
|
|
import acr.browser.lightning.app.BrowserApp; |
|
|
|
import acr.browser.lightning.constant.Constants; |
|
|
|
import acr.browser.lightning.constant.Constants; |
|
|
|
|
|
|
|
import acr.browser.lightning.database.downloads.DownloadItem; |
|
|
|
|
|
|
|
import acr.browser.lightning.database.downloads.DownloadsModel; |
|
|
|
import acr.browser.lightning.dialog.BrowserDialog; |
|
|
|
import acr.browser.lightning.dialog.BrowserDialog; |
|
|
|
import acr.browser.lightning.preference.PreferenceManager; |
|
|
|
import acr.browser.lightning.preference.PreferenceManager; |
|
|
|
|
|
|
|
import acr.browser.lightning.utils.FileUtils; |
|
|
|
import acr.browser.lightning.utils.Utils; |
|
|
|
import acr.browser.lightning.utils.Utils; |
|
|
|
|
|
|
|
import acr.browser.lightning.view.LightningView; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Handle download requests |
|
|
|
* Handle download requests |
|
|
@ -43,9 +53,11 @@ public class DownloadHandler { |
|
|
|
|
|
|
|
|
|
|
|
private static final String COOKIE_REQUEST_HEADER = "Cookie"; |
|
|
|
private static final String COOKIE_REQUEST_HEADER = "Cookie"; |
|
|
|
|
|
|
|
|
|
|
|
public static final String DEFAULT_DOWNLOAD_PATH = |
|
|
|
@Inject DownloadsModel downloadsModel; |
|
|
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) |
|
|
|
|
|
|
|
.getPath(); |
|
|
|
public DownloadHandler() { |
|
|
|
|
|
|
|
BrowserApp.getAppComponent().inject(this); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Notify the host application a download should be done, or that the data |
|
|
|
* Notify the host application a download should be done, or that the data |
|
|
@ -56,9 +68,10 @@ public class DownloadHandler { |
|
|
|
* @param userAgent User agent of the downloading application. |
|
|
|
* @param userAgent User agent of the downloading application. |
|
|
|
* @param contentDisposition Content-disposition http header, if present. |
|
|
|
* @param contentDisposition Content-disposition http header, if present. |
|
|
|
* @param mimetype The mimetype of the content reported by the server |
|
|
|
* @param mimetype The mimetype of the content reported by the server |
|
|
|
|
|
|
|
* @param contentSize The size of the content |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static void onDownloadStart(@NonNull Activity context, @NonNull PreferenceManager manager, String url, String userAgent, |
|
|
|
public void onDownloadStart(@NonNull Activity context, @NonNull PreferenceManager manager, String url, String userAgent, |
|
|
|
@Nullable String contentDisposition, String mimetype) { |
|
|
|
@Nullable String contentDisposition, String mimetype, String contentSize) { |
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "DOWNLOAD: Trying to download from URL: " + url); |
|
|
|
Log.d(TAG, "DOWNLOAD: Trying to download from URL: " + url); |
|
|
|
Log.d(TAG, "DOWNLOAD: Content disposition: " + contentDisposition); |
|
|
|
Log.d(TAG, "DOWNLOAD: Content disposition: " + contentDisposition); |
|
|
@ -68,7 +81,7 @@ public class DownloadHandler { |
|
|
|
// if we're dealing wih A/V content that's not explicitly marked
|
|
|
|
// if we're dealing wih A/V content that's not explicitly marked
|
|
|
|
// for download, check if it's streamable.
|
|
|
|
// for download, check if it's streamable.
|
|
|
|
if (contentDisposition == null |
|
|
|
if (contentDisposition == null |
|
|
|
|| !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) { |
|
|
|
|| !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) { |
|
|
|
// query the package manager to see if there's a registered handler
|
|
|
|
// query the package manager to see if there's a registered handler
|
|
|
|
// that matches.
|
|
|
|
// that matches.
|
|
|
|
Intent intent = new Intent(Intent.ACTION_VIEW); |
|
|
|
Intent intent = new Intent(Intent.ACTION_VIEW); |
|
|
@ -80,12 +93,12 @@ public class DownloadHandler { |
|
|
|
intent.setSelector(null); |
|
|
|
intent.setSelector(null); |
|
|
|
} |
|
|
|
} |
|
|
|
ResolveInfo info = context.getPackageManager().resolveActivity(intent, |
|
|
|
ResolveInfo info = context.getPackageManager().resolveActivity(intent, |
|
|
|
PackageManager.MATCH_DEFAULT_ONLY); |
|
|
|
PackageManager.MATCH_DEFAULT_ONLY); |
|
|
|
if (info != null) { |
|
|
|
if (info != null) { |
|
|
|
// If we resolved to ourselves, we don't want to attempt to
|
|
|
|
// If we resolved to ourselves, we don't want to attempt to
|
|
|
|
// load the url only to try and download it again.
|
|
|
|
// load the url only to try and download it again.
|
|
|
|
if (BuildConfig.APPLICATION_ID.equals(info.activityInfo.packageName) |
|
|
|
if (BuildConfig.APPLICATION_ID.equals(info.activityInfo.packageName) |
|
|
|
|| MainActivity.class.getName().equals(info.activityInfo.name)) { |
|
|
|
|| MainActivity.class.getName().equals(info.activityInfo.name)) { |
|
|
|
// someone (other than us) knows how to handle this mime
|
|
|
|
// someone (other than us) knows how to handle this mime
|
|
|
|
// type with this scheme, don't download.
|
|
|
|
// type with this scheme, don't download.
|
|
|
|
try { |
|
|
|
try { |
|
|
@ -98,7 +111,7 @@ public class DownloadHandler { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
onDownloadStartNoStream(context, manager, url, userAgent, contentDisposition, mimetype); |
|
|
|
onDownloadStartNoStream(context, manager, url, userAgent, contentDisposition, mimetype, contentSize); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// This is to work around the fact that java.net.URI throws Exceptions
|
|
|
|
// This is to work around the fact that java.net.URI throws Exceptions
|
|
|
@ -141,11 +154,12 @@ public class DownloadHandler { |
|
|
|
* @param userAgent User agent of the downloading application. |
|
|
|
* @param userAgent User agent of the downloading application. |
|
|
|
* @param contentDisposition Content-disposition http header, if present. |
|
|
|
* @param contentDisposition Content-disposition http header, if present. |
|
|
|
* @param mimetype The mimetype of the content reported by the server |
|
|
|
* @param mimetype The mimetype of the content reported by the server |
|
|
|
|
|
|
|
* @param contentSize The size of the content |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
/* package */ |
|
|
|
/* package */ |
|
|
|
private static void onDownloadStartNoStream(@NonNull final Activity context, @NonNull PreferenceManager preferences, |
|
|
|
private void onDownloadStartNoStream(@NonNull final Activity context, @NonNull PreferenceManager preferences, |
|
|
|
String url, String userAgent, |
|
|
|
String url, String userAgent, |
|
|
|
String contentDisposition, @Nullable String mimetype) { |
|
|
|
String contentDisposition, @Nullable String mimetype, String contentSize) { |
|
|
|
final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); |
|
|
|
final String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); |
|
|
|
|
|
|
|
|
|
|
|
// Check to see if we have an SDCard
|
|
|
|
// Check to see if we have an SDCard
|
|
|
@ -164,8 +178,8 @@ public class DownloadHandler { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Dialog dialog = new AlertDialog.Builder(context).setTitle(title) |
|
|
|
Dialog dialog = new AlertDialog.Builder(context).setTitle(title) |
|
|
|
.setIcon(android.R.drawable.ic_dialog_alert).setMessage(msg) |
|
|
|
.setIcon(android.R.drawable.ic_dialog_alert).setMessage(msg) |
|
|
|
.setPositiveButton(R.string.action_ok, null).show(); |
|
|
|
.setPositiveButton(R.string.action_ok, null).show(); |
|
|
|
BrowserDialog.setDialogSize(context, dialog); |
|
|
|
BrowserDialog.setDialogSize(context, dialog); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
@ -198,16 +212,8 @@ public class DownloadHandler { |
|
|
|
// or, should it be set to one of several Environment.DIRECTORY* dirs
|
|
|
|
// or, should it be set to one of several Environment.DIRECTORY* dirs
|
|
|
|
// depending on mimetype?
|
|
|
|
// depending on mimetype?
|
|
|
|
String location = preferences.getDownloadDirectory(); |
|
|
|
String location = preferences.getDownloadDirectory(); |
|
|
|
Uri downloadFolder; |
|
|
|
location = FileUtils.addNecessarySlashes(location); |
|
|
|
location = addNecessarySlashes(location); |
|
|
|
Uri downloadFolder = Uri.parse(location); |
|
|
|
downloadFolder = Uri.parse(location); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
File dir = new File(downloadFolder.getPath()); |
|
|
|
|
|
|
|
if (!dir.isDirectory() && !dir.mkdirs()) { |
|
|
|
|
|
|
|
// Cannot make the directory
|
|
|
|
|
|
|
|
Utils.showSnackbar(context, R.string.problem_location_download); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!isWriteAccessAvailable(downloadFolder)) { |
|
|
|
if (!isWriteAccessAvailable(downloadFolder)) { |
|
|
|
Utils.showSnackbar(context, R.string.problem_location_download); |
|
|
|
Utils.showSnackbar(context, R.string.problem_location_download); |
|
|
@ -240,7 +246,7 @@ public class DownloadHandler { |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
Log.d(TAG, "Valid mimetype, attempting to download"); |
|
|
|
Log.d(TAG, "Valid mimetype, attempting to download"); |
|
|
|
final DownloadManager manager = (DownloadManager) context |
|
|
|
final DownloadManager manager = (DownloadManager) context |
|
|
|
.getSystemService(Context.DOWNLOAD_SERVICE); |
|
|
|
.getSystemService(Context.DOWNLOAD_SERVICE); |
|
|
|
try { |
|
|
|
try { |
|
|
|
manager.enqueue(request); |
|
|
|
manager.enqueue(request); |
|
|
|
} catch (IllegalArgumentException e) { |
|
|
|
} catch (IllegalArgumentException e) { |
|
|
@ -254,80 +260,30 @@ public class DownloadHandler { |
|
|
|
} |
|
|
|
} |
|
|
|
Utils.showSnackbar(context, context.getString(R.string.download_pending) + ' ' + filename); |
|
|
|
Utils.showSnackbar(context, context.getString(R.string.download_pending) + ' ' + filename); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final String sFileName = "test"; |
|
|
|
|
|
|
|
private static final String sFileExtension = ".txt"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
// save download in database
|
|
|
|
* Determine whether there is write access in the given directory. Returns false if a |
|
|
|
BrowserActivity browserActivity = (BrowserActivity) context; |
|
|
|
* file cannot be created in the directory or if the directory does not exist. |
|
|
|
LightningView view = browserActivity.getTabModel().getCurrentTab(); |
|
|
|
* |
|
|
|
|
|
|
|
* @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(@Nullable 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()) { |
|
|
|
|
|
|
|
//noinspection ResultOfMethodCallIgnored
|
|
|
|
|
|
|
|
file.delete(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} catch (IOException ignored) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
file = new File(dir + sFileName + '-' + n + sFileExtension); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return file.canWrite(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
if (view != null && !view.isIncognito()) { |
|
|
|
* Returns the first parent directory of a directory that exists. This is useful |
|
|
|
downloadsModel.addDownloadIfNotExists(new DownloadItem(url, filename, contentSize)) |
|
|
|
* for subdirectories that do not exist but their parents do. |
|
|
|
.subscribe(new SingleOnSubscribe<Boolean>() { |
|
|
|
* |
|
|
|
@Override |
|
|
|
* @param directory the directory to find the first existent parent |
|
|
|
public void onItem(@Nullable Boolean item) { |
|
|
|
* @return the first existent parent |
|
|
|
if (item != null && !item) |
|
|
|
*/ |
|
|
|
Log.i(TAG, "error saving download to database"); |
|
|
|
@Nullable |
|
|
|
} |
|
|
|
private static String getFirstRealParentDirectory(@Nullable String directory) { |
|
|
|
}); |
|
|
|
while (true) { |
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
directory = parent.substring(0, previousIndex); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return "/"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return "/"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return directory; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static boolean isWriteAccessAvailable(@NonNull Uri fileUri) { |
|
|
|
private static boolean isWriteAccessAvailable(@NonNull Uri fileUri) { |
|
|
|
File file = new File(fileUri.getPath()); |
|
|
|
File file = new File(fileUri.getPath()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!file.isDirectory() && !file.mkdirs()) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
if (file.createNewFile()) { |
|
|
|
if (file.createNewFile()) { |
|
|
|
//noinspection ResultOfMethodCallIgnored
|
|
|
|
//noinspection ResultOfMethodCallIgnored
|
|
|
@ -338,19 +294,4 @@ public class DownloadHandler { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
@NonNull |
|
|
|
|
|
|
|
public static String addNecessarySlashes(@Nullable 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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|