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.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.net.Uri; 30 import android.net.WebAddress; 31 import android.os.Environment; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.webkit.CookieManager; 35 import android.webkit.URLUtil; 36 import android.widget.Toast; 37 38 /** 39 * Handle download requests 40 */ 41 public class DownloadHandler { 42 43 private static final boolean LOGD_ENABLED = 44 com.android.browser.Browser.LOGD_ENABLED; 45 46 private static final String LOGTAG = "DLHandler"; 47 48 /** 49 * Notify the host application a download should be done, or that 50 * the data should be streamed if a streaming viewer is available. 51 * @param activity Activity requesting the download. 52 * @param url The full url to the content that should be downloaded 53 * @param userAgent User agent of the downloading application. 54 * @param contentDisposition Content-disposition http header, if present. 55 * @param mimetype The mimetype of the content reported by the server 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, boolean privateBrowsing)58 public static void onDownloadStart(Activity activity, String url, 59 String userAgent, String contentDisposition, String mimetype, 60 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, 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 == ']') { 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 == ']') { 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 privateBrowsing If the request is coming from a private browsing tab. 140 */ onDownloadStartNoStream(Activity activity, String url, String userAgent, String contentDisposition, String mimetype, boolean privateBrowsing)141 /*package */ static void onDownloadStartNoStream(Activity activity, 142 String url, String userAgent, String contentDisposition, 143 String mimetype, boolean privateBrowsing) { 144 145 String filename = URLUtil.guessFileName(url, 146 contentDisposition, mimetype); 147 148 // Check to see if we have an SDCard 149 String status = Environment.getExternalStorageState(); 150 if (!status.equals(Environment.MEDIA_MOUNTED)) { 151 int title; 152 String msg; 153 154 // Check to see if the SDCard is busy, same as the music app 155 if (status.equals(Environment.MEDIA_SHARED)) { 156 msg = activity.getString(R.string.download_sdcard_busy_dlg_msg); 157 title = R.string.download_sdcard_busy_dlg_title; 158 } else { 159 msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename); 160 title = R.string.download_no_sdcard_dlg_title; 161 } 162 163 new AlertDialog.Builder(activity) 164 .setTitle(title) 165 .setIcon(android.R.drawable.ic_dialog_alert) 166 .setMessage(msg) 167 .setPositiveButton(R.string.ok, null) 168 .show(); 169 return; 170 } 171 172 // java.net.URI is a lot stricter than KURL so we have to encode some 173 // extra characters. Fix for b 2538060 and b 1634719 174 WebAddress webAddress; 175 try { 176 webAddress = new WebAddress(url); 177 webAddress.setPath(encodePath(webAddress.getPath())); 178 } catch (Exception e) { 179 // This only happens for very bad urls, we want to chatch the 180 // exception here 181 Log.e(LOGTAG, "Exception trying to parse url:" + url); 182 return; 183 } 184 185 String addressString = webAddress.toString(); 186 Uri uri = Uri.parse(addressString); 187 final DownloadManager.Request request; 188 try { 189 request = new DownloadManager.Request(uri); 190 } catch (IllegalArgumentException e) { 191 Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show(); 192 return; 193 } 194 request.setMimeType(mimetype); 195 // set downloaded file destination to /sdcard/Download. 196 // or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype? 197 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename); 198 // let this downloaded file be scanned by MediaScanner - so that it can 199 // show up in Gallery app, for example. 200 request.allowScanningByMediaScanner(); 201 request.setDescription(webAddress.getHost()); 202 // XXX: Have to use the old url since the cookies were stored using the 203 // old percent-encoded url. 204 String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing); 205 request.addRequestHeader("cookie", cookies); 206 request.setNotificationVisibility( 207 DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); 208 if (mimetype == null) { 209 if (TextUtils.isEmpty(addressString)) { 210 return; 211 } 212 // We must have long pressed on a link or image to download it. We 213 // are not sure of the mimetype in this case, so do a head request 214 new FetchUrlMimeType(activity, request, addressString, cookies, 215 userAgent).start(); 216 } else { 217 final DownloadManager manager 218 = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE); 219 new Thread("Browser download") { 220 public void run() { 221 manager.enqueue(request); 222 } 223 }.start(); 224 } 225 Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT) 226 .show(); 227 } 228 229 } 230