• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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