1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.browser; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.DownloadManager; 22 import android.content.ActivityNotFoundException; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.content.pm.ResolveInfo; 28 import android.net.Uri; 29 import android.net.WebAddress; 30 import android.os.Environment; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.webkit.CookieManager; 34 import android.webkit.URLUtil; 35 import android.widget.Toast; 36 37 /** 38 * Handle download requests 39 */ 40 public class DownloadHandler { 41 42 private static final boolean LOGD_ENABLED = 43 com.android.browser.Browser.LOGD_ENABLED; 44 45 private static final String LOGTAG = "DLHandler"; 46 47 /** 48 * Notify the host application a download should be done, or that 49 * the data should be streamed if a streaming viewer is available. 50 * @param activity Activity requesting the download. 51 * @param url The full url to the content that should be downloaded 52 * @param userAgent User agent of the downloading application. 53 * @param contentDisposition Content-disposition http header, if present. 54 * @param mimetype The mimetype of the content reported by the server 55 * @param referer The referer associated with the downloaded url 56 * @param privateBrowsing If the request is coming from a private browsing tab. 57 */ 58 public static void onDownloadStart(Activity activity, String url, 59 String userAgent, String contentDisposition, String mimetype, 60 String referer, boolean privateBrowsing) { 61 // if we're dealing wih A/V content that's not explicitly marked 62 // for download, check if it's streamable. 63 if (contentDisposition == null 64 || !contentDisposition.regionMatches( 65 true, 0, "attachment", 0, 10)) { 66 // query the package manager to see if there's a registered handler 67 // that matches. 68 Intent intent = new Intent(Intent.ACTION_VIEW); 69 intent.setDataAndType(Uri.parse(url), mimetype); 70 ResolveInfo info = activity.getPackageManager().resolveActivity(intent, 71 PackageManager.MATCH_DEFAULT_ONLY); 72 if (info != null) { 73 ComponentName myName = activity.getComponentName(); 74 // If we resolved to ourselves, we don't want to attempt to 75 // load the url only to try and download it again. 76 if (!myName.getPackageName().equals( 77 info.activityInfo.packageName) 78 || !myName.getClassName().equals( 79 info.activityInfo.name)) { 80 // someone (other than us) knows how to handle this mime 81 // type with this scheme, don't download. 82 try { 83 activity.startActivity(intent); 84 return; 85 } catch (ActivityNotFoundException ex) { 86 if (LOGD_ENABLED) { 87 Log.d(LOGTAG, "activity not found for " + mimetype 88 + " over " + Uri.parse(url).getScheme(), 89 ex); 90 } 91 // Best behavior is to fall back to a download in this 92 // case 93 } 94 } 95 } 96 } 97 onDownloadStartNoStream(activity, url, userAgent, contentDisposition, 98 mimetype, referer, privateBrowsing); 99 } 100 101 // This is to work around the fact that java.net.URI throws Exceptions 102 // instead of just encoding URL's properly 103 // Helper method for onDownloadStartNoStream 104 private static String encodePath(String path) { 105 char[] chars = path.toCharArray(); 106 107 boolean needed = false; 108 for (char c : chars) { 109 if (c == '[' || c == ']' || c == '|') { 110 needed = true; 111 break; 112 } 113 } 114 if (needed == false) { 115 return path; 116 } 117 118 StringBuilder sb = new StringBuilder(""); 119 for (char c : chars) { 120 if (c == '[' || c == ']' || c == '|') { 121 sb.append('%'); 122 sb.append(Integer.toHexString(c)); 123 } else { 124 sb.append(c); 125 } 126 } 127 128 return sb.toString(); 129 } 130 131 /** 132 * Notify the host application a download should be done, even if there 133 * is a streaming viewer available for thise type. 134 * @param activity Activity requesting the download. 135 * @param url The full url to the content that should be downloaded 136 * @param userAgent User agent of the downloading application. 137 * @param contentDisposition Content-disposition http header, if present. 138 * @param mimetype The mimetype of the content reported by the server 139 * @param referer The referer associated with the downloaded url 140 * @param privateBrowsing If the request is coming from a private browsing tab. 141 */ 142 /*package */ static void onDownloadStartNoStream(Activity activity, 143 String url, String userAgent, String contentDisposition, 144 String mimetype, String referer, boolean privateBrowsing) { 145 146 String filename = URLUtil.guessFileName(url, 147 contentDisposition, mimetype); 148 149 // Check to see if we have an SDCard 150 String status = Environment.getExternalStorageState(); 151 if (!status.equals(Environment.MEDIA_MOUNTED)) { 152 int title; 153 String msg; 154 155 // Check to see if the SDCard is busy, same as the music app 156 if (status.equals(Environment.MEDIA_SHARED)) { 157 msg = activity.getString(R.string.download_sdcard_busy_dlg_msg); 158 title = R.string.download_sdcard_busy_dlg_title; 159 } else { 160 msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename); 161 title = R.string.download_no_sdcard_dlg_title; 162 } 163 164 new AlertDialog.Builder(activity) 165 .setTitle(title) 166 .setIconAttribute(android.R.attr.alertDialogIcon) 167 .setMessage(msg) 168 .setPositiveButton(R.string.ok, null) 169 .show(); 170 return; 171 } 172 173 // java.net.URI is a lot stricter than KURL so we have to encode some 174 // extra characters. Fix for b 2538060 and b 1634719 175 WebAddress webAddress; 176 try { 177 webAddress = new WebAddress(url); 178 webAddress.setPath(encodePath(webAddress.getPath())); 179 } catch (Exception e) { 180 // This only happens for very bad urls, we want to chatch the 181 // exception here 182 Log.e(LOGTAG, "Exception trying to parse url:" + url); 183 return; 184 } 185 186 String addressString = webAddress.toString(); 187 Uri uri = Uri.parse(addressString); 188 final DownloadManager.Request request; 189 try { 190 request = new DownloadManager.Request(uri); 191 } catch (IllegalArgumentException e) { 192 Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show(); 193 return; 194 } 195 request.setMimeType(mimetype); 196 // set downloaded file destination to /sdcard/Download. 197 // or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype? 198 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); 199 // let this downloaded file be scanned by MediaScanner - so that it can 200 // show up in Gallery app, for example. 201 request.allowScanningByMediaScanner(); 202 request.setDescription(webAddress.getHost()); 203 // XXX: Have to use the old url since the cookies were stored using the 204 // old percent-encoded url. 205 String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing); 206 request.addRequestHeader("cookie", cookies); 207 request.addRequestHeader("User-Agent", userAgent); 208 request.addRequestHeader("Referer", referer); 209 request.setNotificationVisibility( 210 DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 211 if (mimetype == null) { 212 if (TextUtils.isEmpty(addressString)) { 213 return; 214 } 215 // We must have long pressed on a link or image to download it. We 216 // are not sure of the mimetype in this case, so do a head request 217 new FetchUrlMimeType(activity, request, addressString, cookies, 218 userAgent).start(); 219 } else { 220 final DownloadManager manager 221 = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE); 222 new Thread("Browser download") { 223 public void run() { 224 manager.enqueue(request); 225 } 226 }.start(); 227 } 228 Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT) 229 .show(); 230 } 231 232 } 233