blob: 46e6cbcbbb8c4fc2c6f2023fe7b77ac47dc8861f [file] [log] [blame]
Michael Kolb8233fac2010-10-26 16:08:53 -07001/*
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
17package com.android.browser;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.DownloadManager;
22import android.content.ActivityNotFoundException;
23import android.content.ComponentName;
Michael Kolb8233fac2010-10-26 16:08:53 -070024import android.content.Context;
qqzhoua95a2e22013-04-18 17:28:31 +080025import android.content.DialogInterface;
Michael Kolb8233fac2010-10-26 16:08:53 -070026import android.content.Intent;
27import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
qqzhoua95a2e22013-04-18 17:28:31 +080029import android.media.MediaFile;
Michael Kolb8233fac2010-10-26 16:08:53 -070030import android.net.Uri;
31import android.net.WebAddress;
32import android.os.Environment;
Leon Scroggins63c02662010-11-18 15:16:27 -050033import android.text.TextUtils;
Michael Kolb8233fac2010-10-26 16:08:53 -070034import android.util.Log;
35import android.webkit.CookieManager;
36import android.webkit.URLUtil;
37import android.widget.Toast;
38
39/**
40 * Handle download requests
41 */
42public class DownloadHandler {
43
44 private static final boolean LOGD_ENABLED =
45 com.android.browser.Browser.LOGD_ENABLED;
46
47 private static final String LOGTAG = "DLHandler";
48
Michael Kolb8233fac2010-10-26 16:08:53 -070049 /**
50 * Notify the host application a download should be done, or that
51 * the data should be streamed if a streaming viewer is available.
Leon Scroggins63c02662010-11-18 15:16:27 -050052 * @param activity Activity requesting the download.
Michael Kolb8233fac2010-10-26 16:08:53 -070053 * @param url The full url to the content that should be downloaded
Leon Scroggins63c02662010-11-18 15:16:27 -050054 * @param userAgent User agent of the downloading application.
55 * @param contentDisposition Content-disposition http header, if present.
Michael Kolb8233fac2010-10-26 16:08:53 -070056 * @param mimetype The mimetype of the content reported by the server
Selim Gurun0b3d66f2012-08-29 13:08:13 -070057 * @param referer The referer associated with the downloaded url
Kristian Monsenbc5cc752011-03-02 13:14:03 +000058 * @param privateBrowsing If the request is coming from a private browsing tab.
Michael Kolb8233fac2010-10-26 16:08:53 -070059 */
qqzhoua95a2e22013-04-18 17:28:31 +080060 public static boolean onDownloadStart(final Activity activity, final String url,
61 final String userAgent, final String contentDisposition, final String mimetype,
62 final String referer, final boolean privateBrowsing) {
Michael Kolb8233fac2010-10-26 16:08:53 -070063 // if we're dealing wih A/V content that's not explicitly marked
64 // for download, check if it's streamable.
65 if (contentDisposition == null
66 || !contentDisposition.regionMatches(
67 true, 0, "attachment", 0, 10)) {
qqzhoua95a2e22013-04-18 17:28:31 +080068
69 // Add for Carrier Feature - When open an audio/video link, prompt a dialog
70 // to let the user choose play or download operation.
71 Uri uri = Uri.parse(url);
72 String scheme = uri.getScheme();
73 Log.v(LOGTAG, "scheme:" + scheme + ", mimetype:" + mimetype);
74 // Some mimetype for audio/video files is not started with "audio" or "video",
75 // such as ogg audio file with mimetype "application/ogg". So we also check
76 // file type by MediaFile.isAudioFileType() and MediaFile.isVideoFileType().
77 // For those file types other than audio or video, download it immediately.
78 int fileType = MediaFile.getFileTypeForMimeType(mimetype);
79 if ("http".equalsIgnoreCase(scheme) &&
80 (mimetype.startsWith("audio/") ||
81 mimetype.startsWith("video/") ||
82 MediaFile.isAudioFileType(fileType) ||
83 MediaFile.isVideoFileType(fileType))) {
84 new AlertDialog.Builder(activity)
85 .setTitle(R.string.application_name)
86 .setIcon(R.drawable.default_video_poster)
87 .setMessage(R.string.http_video_msg)
88 .setPositiveButton(R.string.video_save, new DialogInterface.OnClickListener() {
89 public void onClick(DialogInterface dialog, int which) {
90 onDownloadStartNoStream(activity, url, userAgent, contentDisposition,
91 mimetype, referer, privateBrowsing);
92 }
93 })
94 .setNegativeButton(R.string.video_play, new DialogInterface.OnClickListener() {
95 public void onClick(DialogInterface dialog, int which) {
96 Intent intent = new Intent(Intent.ACTION_VIEW);
97 intent.setDataAndType(Uri.parse(url), mimetype);
98 try {
99 String title = URLUtil.guessFileName(url, contentDisposition, mimetype);
100 intent.putExtra(Intent.EXTRA_TITLE, title);
101 activity.startActivity(intent);
102 } catch (ActivityNotFoundException ex) {
103 Log.w(LOGTAG, "When http stream play, activity not found for "
104 + mimetype + " over " + Uri.parse(url).getScheme(),
105 ex);
106 }
107 }
108 }).show();
109
110 return true;
111 }
112
Michael Kolb8233fac2010-10-26 16:08:53 -0700113 // query the package manager to see if there's a registered handler
114 // that matches.
115 Intent intent = new Intent(Intent.ACTION_VIEW);
116 intent.setDataAndType(Uri.parse(url), mimetype);
Leon Scroggins63c02662010-11-18 15:16:27 -0500117 ResolveInfo info = activity.getPackageManager().resolveActivity(intent,
Michael Kolb8233fac2010-10-26 16:08:53 -0700118 PackageManager.MATCH_DEFAULT_ONLY);
119 if (info != null) {
Leon Scroggins63c02662010-11-18 15:16:27 -0500120 ComponentName myName = activity.getComponentName();
Michael Kolb8233fac2010-10-26 16:08:53 -0700121 // If we resolved to ourselves, we don't want to attempt to
122 // load the url only to try and download it again.
123 if (!myName.getPackageName().equals(
124 info.activityInfo.packageName)
125 || !myName.getClassName().equals(
126 info.activityInfo.name)) {
127 // someone (other than us) knows how to handle this mime
128 // type with this scheme, don't download.
129 try {
Leon Scroggins63c02662010-11-18 15:16:27 -0500130 activity.startActivity(intent);
qqzhoua95a2e22013-04-18 17:28:31 +0800131 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -0700132 } catch (ActivityNotFoundException ex) {
133 if (LOGD_ENABLED) {
134 Log.d(LOGTAG, "activity not found for " + mimetype
135 + " over " + Uri.parse(url).getScheme(),
136 ex);
137 }
138 // Best behavior is to fall back to a download in this
139 // case
140 }
141 }
142 }
143 }
Leon Scroggins63c02662010-11-18 15:16:27 -0500144 onDownloadStartNoStream(activity, url, userAgent, contentDisposition,
Selim Gurun0b3d66f2012-08-29 13:08:13 -0700145 mimetype, referer, privateBrowsing);
qqzhoua95a2e22013-04-18 17:28:31 +0800146 return false;
Michael Kolb8233fac2010-10-26 16:08:53 -0700147 }
148
149 // This is to work around the fact that java.net.URI throws Exceptions
150 // instead of just encoding URL's properly
151 // Helper method for onDownloadStartNoStream
152 private static String encodePath(String path) {
153 char[] chars = path.toCharArray();
154
155 boolean needed = false;
156 for (char c : chars) {
Selim Guruna770f8d2012-06-13 14:51:23 -0700157 if (c == '[' || c == ']' || c == '|') {
Michael Kolb8233fac2010-10-26 16:08:53 -0700158 needed = true;
159 break;
160 }
161 }
162 if (needed == false) {
163 return path;
164 }
165
166 StringBuilder sb = new StringBuilder("");
167 for (char c : chars) {
Selim Guruna770f8d2012-06-13 14:51:23 -0700168 if (c == '[' || c == ']' || c == '|') {
Michael Kolb8233fac2010-10-26 16:08:53 -0700169 sb.append('%');
170 sb.append(Integer.toHexString(c));
171 } else {
172 sb.append(c);
173 }
174 }
175
176 return sb.toString();
177 }
178
179 /**
180 * Notify the host application a download should be done, even if there
181 * is a streaming viewer available for thise type.
Leon Scroggins63c02662010-11-18 15:16:27 -0500182 * @param activity Activity requesting the download.
Michael Kolb8233fac2010-10-26 16:08:53 -0700183 * @param url The full url to the content that should be downloaded
Leon Scroggins63c02662010-11-18 15:16:27 -0500184 * @param userAgent User agent of the downloading application.
185 * @param contentDisposition Content-disposition http header, if present.
Michael Kolb8233fac2010-10-26 16:08:53 -0700186 * @param mimetype The mimetype of the content reported by the server
Selim Gurun0b3d66f2012-08-29 13:08:13 -0700187 * @param referer The referer associated with the downloaded url
Kristian Monsenbc5cc752011-03-02 13:14:03 +0000188 * @param privateBrowsing If the request is coming from a private browsing tab.
Michael Kolb8233fac2010-10-26 16:08:53 -0700189 */
Leon Scroggins63c02662010-11-18 15:16:27 -0500190 /*package */ static void onDownloadStartNoStream(Activity activity,
191 String url, String userAgent, String contentDisposition,
Selim Gurun0b3d66f2012-08-29 13:08:13 -0700192 String mimetype, String referer, boolean privateBrowsing) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700193
194 String filename = URLUtil.guessFileName(url,
195 contentDisposition, mimetype);
196
197 // Check to see if we have an SDCard
198 String status = Environment.getExternalStorageState();
199 if (!status.equals(Environment.MEDIA_MOUNTED)) {
200 int title;
201 String msg;
202
203 // Check to see if the SDCard is busy, same as the music app
204 if (status.equals(Environment.MEDIA_SHARED)) {
Leon Scroggins63c02662010-11-18 15:16:27 -0500205 msg = activity.getString(R.string.download_sdcard_busy_dlg_msg);
Michael Kolb8233fac2010-10-26 16:08:53 -0700206 title = R.string.download_sdcard_busy_dlg_title;
207 } else {
Leon Scroggins63c02662010-11-18 15:16:27 -0500208 msg = activity.getString(R.string.download_no_sdcard_dlg_msg, filename);
Michael Kolb8233fac2010-10-26 16:08:53 -0700209 title = R.string.download_no_sdcard_dlg_title;
210 }
211
Leon Scroggins63c02662010-11-18 15:16:27 -0500212 new AlertDialog.Builder(activity)
Michael Kolb8233fac2010-10-26 16:08:53 -0700213 .setTitle(title)
Björn Lundén2aa8ba22012-05-31 23:05:56 +0200214 .setIconAttribute(android.R.attr.alertDialogIcon)
Michael Kolb8233fac2010-10-26 16:08:53 -0700215 .setMessage(msg)
216 .setPositiveButton(R.string.ok, null)
217 .show();
218 return;
219 }
220
221 // java.net.URI is a lot stricter than KURL so we have to encode some
222 // extra characters. Fix for b 2538060 and b 1634719
223 WebAddress webAddress;
224 try {
225 webAddress = new WebAddress(url);
226 webAddress.setPath(encodePath(webAddress.getPath()));
227 } catch (Exception e) {
228 // This only happens for very bad urls, we want to chatch the
229 // exception here
230 Log.e(LOGTAG, "Exception trying to parse url:" + url);
231 return;
232 }
233
234 String addressString = webAddress.toString();
235 Uri uri = Uri.parse(addressString);
Leon Scroggins11e309c2011-02-01 13:37:14 -0500236 final DownloadManager.Request request;
237 try {
238 request = new DownloadManager.Request(uri);
239 } catch (IllegalArgumentException e) {
240 Toast.makeText(activity, R.string.cannot_download, Toast.LENGTH_SHORT).show();
241 return;
242 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700243 request.setMimeType(mimetype);
Vasu Noric2df8342010-12-18 20:15:46 -0800244 // set downloaded file destination to /sdcard/Download.
245 // or, should it be set to one of several Environment.DIRECTORY* dirs depending on mimetype?
246 request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename);
Michael Kolb8233fac2010-10-26 16:08:53 -0700247 // let this downloaded file be scanned by MediaScanner - so that it can
248 // show up in Gallery app, for example.
249 request.allowScanningByMediaScanner();
250 request.setDescription(webAddress.getHost());
Leon Scroggins63c02662010-11-18 15:16:27 -0500251 // XXX: Have to use the old url since the cookies were stored using the
252 // old percent-encoded url.
Kristian Monsenbc5cc752011-03-02 13:14:03 +0000253 String cookies = CookieManager.getInstance().getCookie(url, privateBrowsing);
Michael Kolb8233fac2010-10-26 16:08:53 -0700254 request.addRequestHeader("cookie", cookies);
Patrik Stenkilssond0fc5892012-02-14 09:33:24 +0100255 request.addRequestHeader("User-Agent", userAgent);
Selim Gurun0b3d66f2012-08-29 13:08:13 -0700256 request.addRequestHeader("Referer", referer);
Michael Kolb8233fac2010-10-26 16:08:53 -0700257 request.setNotificationVisibility(
258 DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
259 if (mimetype == null) {
Leon Scroggins63c02662010-11-18 15:16:27 -0500260 if (TextUtils.isEmpty(addressString)) {
261 return;
262 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700263 // We must have long pressed on a link or image to download it. We
264 // are not sure of the mimetype in this case, so do a head request
Leon Scroggins63c02662010-11-18 15:16:27 -0500265 new FetchUrlMimeType(activity, request, addressString, cookies,
266 userAgent).start();
Michael Kolb8233fac2010-10-26 16:08:53 -0700267 } else {
Leon Scroggins63c02662010-11-18 15:16:27 -0500268 final DownloadManager manager
269 = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
270 new Thread("Browser download") {
271 public void run() {
272 manager.enqueue(request);
273 }
274 }.start();
Michael Kolb8233fac2010-10-26 16:08:53 -0700275 }
Leon Scroggins63c02662010-11-18 15:16:27 -0500276 Toast.makeText(activity, R.string.download_pending, Toast.LENGTH_SHORT)
Michael Kolb8233fac2010-10-26 16:08:53 -0700277 .show();
278 }
279
280}