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