/* * Copyright 2014 A.C.R. Development */ package acr.browser.lightning; import android.app.Activity; import android.app.AlertDialog; import android.app.DownloadManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.webkit.CookieManager; import android.webkit.URLUtil; import android.widget.Toast; /** * Handle download requests */ public class DownloadHandler { private static final String LOGTAG = "DLHandler"; /** * Notify the host application a download should be done, or that the data * should be streamed if a streaming viewer is available. * * @param activity * Activity requesting the download. * @param url * The full url to the content that should be downloaded * @param userAgent * User agent of the downloading application. * @param contentDisposition * Content-disposition http header, if present. * @param mimetype * The mimetype of the content reported by the server * @param privateBrowsing * If the request is coming from a private browsing tab. */ public static void onDownloadStart(Activity activity, String url, String userAgent, String contentDisposition, String mimetype, boolean privateBrowsing) { // if we're dealing wih A/V content that's not explicitly marked // for download, check if it's streamable. if (contentDisposition == null || !contentDisposition.regionMatches(true, 0, "attachment", 0, 10)) { // query the package manager to see if there's a registered handler // that matches. Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(url), mimetype); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ResolveInfo info = activity.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); if (info != null) { ComponentName myName = activity.getComponentName(); // If we resolved to ourselves, we don't want to attempt to // load the url only to try and download it again. if (!myName.getPackageName().equals(info.activityInfo.packageName) || !myName.getClassName().equals(info.activityInfo.name)) { // someone (other than us) knows how to handle this mime // type with this scheme, don't download. try { activity.startActivity(intent); return; } catch (ActivityNotFoundException ex) { // Best behavior is to fall back to a download in this // case } } } } onDownloadStartNoStream(activity, url, userAgent, contentDisposition, mimetype, privateBrowsing); } // This is to work around the fact that java.net.URI throws Exceptions // instead of just encoding URL's properly // Helper method for onDownloadStartNoStream private static String encodePath(String path) { char[] chars = path.toCharArray(); boolean needed = false; for (char c : chars) { if (c == '[' || c == ']' || c == '|') { needed = true; break; } } if (!needed) { return path; } StringBuilder sb = new StringBuilder(""); for (char c : chars) { if (c == '[' || c == ']' || c == '|') { sb.append('%'); sb.append(Integer.toHexString(c)); } else { sb.append(c); } } return sb.toString(); } /** * Notify the host application a download should be done, even if there is a * streaming viewer available for thise type. * * @param activity * Activity requesting the download. * @param url * The full url to the content that should be downloaded * @param userAgent * User agent of the downloading application. * @param contentDisposition * Content-disposition http header, if present. * @param mimetype * The mimetype of the content reported by the server * @param privateBrowsing * If the request is coming from a private browsing tab. */ /* package */ static void onDownloadStartNoStream(Activity activity, String url, String userAgent, String contentDisposition, String mimetype, boolean privateBrowsing) { String filename = URLUtil.guessFileName(url, contentDisposition, mimetype); // Check to see if we have an SDCard String status = Environment.getExternalStorageState(); if (!status.equals(Environment.MEDIA_MOUNTED)) { int title; String msg; // Check to see if the SDCard is busy, same as the music app if (status.equals(Environment.MEDIA_SHARED)) { msg = activity.getString(R.string.download_sdcard_busy_dlg_msg); title = R.string.download_sdcard_busy_dlg_title; } else { msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename); title = R.string.download_no_sdcard_dlg_title; } new AlertDialog.Builder(activity).setTitle(title) .setIcon(android.R.drawable.ic_dialog_alert).setMessage(msg) .setPositiveButton(R.string.action_ok, null).show(); return; } // java.net.URI is a lot stricter than KURL so we have to encode some // extra characters. Fix for b 2538060 and b 1634719 WebAddress webAddress; try { webAddress = new WebAddress(url); webAddress.setPath(encodePath(webAddress.getPath())); } catch (Exception e) { // 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); return; } String addressString = webAddress.toString(); Uri uri = Uri.parse(addressString); final DownloadManager.Request request; try { request = new DownloadManager.Request(uri); } catch (IllegalArgumentException e) { Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show(); return; } request.setMimeType(mimetype); // set downloaded file destination to /sdcard/Download. // or, should it be set to one of several Environment.DIRECTORY* dirs // depending on mimetype? String location = PreferenceManager.getInstance().getDownloadDirectory(); request.setDestinationInExternalPublicDir(location, filename); // let this downloaded file be scanned by MediaScanner - so that it can // show up in Gallery app, for example. request.allowScanningByMediaScanner(); request.setDescription(webAddress.getHost()); // XXX: Have to use the old url since the cookies were stored using the // old percent-encoded url. String cookies = CookieManager.getInstance().getCookie(url); request.addRequestHeader("cookie", cookies); request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); if (mimetype == null) { if (TextUtils.isEmpty(addressString)) { return; } // We must have long pressed on a link or image to download it. We // are not sure of the mimetype in this case, so do a head request new FetchUrlMimeType(activity, request, addressString, cookies, userAgent).start(); } else { final DownloadManager manager = (DownloadManager) activity .getSystemService(Context.DOWNLOAD_SERVICE); new Thread("Browser download") { @Override public void run() { manager.enqueue(request); } }.start(); Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT).show(); } } }