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 */ onDownloadStart(Activity activity, String url, String userAgent, String contentDisposition, String mimetype, String referer, boolean privateBrowsing)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 encodePath(String path)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 */ onDownloadStartNoStream(Activity activity, String url, String userAgent, String contentDisposition, String mimetype, String referer, boolean privateBrowsing)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 try { 199 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); 200 } catch (IllegalStateException ex) { 201 // This only happens when directory Downloads can't be created or it isn't a directory 202 // this is most commonly due to temporary problems with sdcard so show appropriate string 203 Log.w(LOGTAG, "Exception trying to create Download dir:", ex); 204 Toast.makeText(activity, R.string.download_sdcard_busy_dlg_title, 205 Toast.LENGTH_SHORT).show(); 206 return; 207 } 208 // let this downloaded file be scanned by MediaScanner - so that it can 209 // show up in Gallery app, for example. 210 request.allowScanningByMediaScanner(); 211 request.setDescription(webAddress.getHost()); 212 // XXX: Have to use the old url since the cookies were stored using the 213 // old percent-encoded url. 214 String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing); 215 request.addRequestHeader("cookie", cookies); 216 request.addRequestHeader("User-Agent", userAgent); 217 request.addRequestHeader("Referer", referer); 218 request.setNotificationVisibility( 219 DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 220 if (mimetype == null) { 221 if (TextUtils.isEmpty(addressString)) { 222 return; 223 } 224 // We must have long pressed on a link or image to download it. We 225 // are not sure of the mimetype in this case, so do a head request 226 new FetchUrlMimeType(activity, request, addressString, cookies, 227 userAgent).start(); 228 } else { 229 final DownloadManager manager 230 = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE); 231 new Thread("Browser download") { 232 public void run() { 233 manager.enqueue(request); 234 } 235 }.start(); 236 } 237 Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT) 238 .show(); 239 } 240 241 } 242