android内部提供了一个downloadprovider,是一个非常完整的下载工具,提供了很好的外部接口可以被其他应用程序调用,来完成下载工作。同时也提供和很好的下载、通知、存储等机制。
在android的browser等工具里面都用到了这个downloadprovider。
但是很遗憾的是,这个downloadprovider不对app开发人员开放,只作为内部使用。
我们现在去探究如何将downloadprovider拿来给自己用。
让我们先找到downloadprovider不能用的原因:
先找到它的源代码,在这个位置:/packages/providers/downloadprovider
打开androidmanifest.xml文件,里面有几个自定义的权限
<!-- allows access to the download manager -->
<permission android:name="android.permission.access_download_manager"
android:label="@string/permlab_downloadmanager"
android:description="@string/permdesc_downloadmanager"
android:protectionlevel="signatureorsystem" />
<!-- allows advanced access to the download manager -->
<permission android:name="android.permission.access_download_manager_advanced"
android:label="@string/permlab_downloadmanageradvanced"
android:description="@string/permdesc_downloadmanageradvanced"
<!-- allows filesystem access to /cache -->
<permission android:name="android.permission.access_cache_filesystem"
android:label="@string/permlab_cachefilesystem"
android:description="@string/permdesc_cachefilesystem"
android:protectionlevel="signature" />
<!-- allows to send download completed intents -->
<permission android:name="android.permission.send_download_completed_intents"
android:label="@string/permlab_downloadcompletedintent"
android:description="@string/permdesc_downloadcompletedintent"
这几个权限里面都是android:protectionlevel="signatureorsystem" 或者 android:protectionlevel="signature", 这个意思是只有你的app拥有system权限,或者和系统一样的签名,才能调用它。
这里是问题的关键。那我们有两种思路:
一种思路是:将这个protectionlevel改成normal,重新编译downloadprovider工程,让其他app可以直接调用。
另一种思路是:将你自己的app弄成system权限或者和系统一样的签名。
前一种思路已经完全成功了,第二种思路验证了一部分。
先看第一种思路的办法:
1)先将上面几个权限都改成:android:protectionlevel="normal"
2)重新编译downloadprovider
mmm packages/providers/downloadprovider
3) 将编译后的apk替换现有的apk
因为downloadprovider.apk是系统app,你可以先给/system以root权限,然后将这个app替换掉。 (作为一个用户app安装也可以,不过重启以后就没有了)
使用类似 # mount -t ubifs -o remount ubi0:system /system 或者 # mount -o remount ubi0:system /system 给/system rw权限。
然后通过adb push 将downloadprovider.apk push到 /system/app/下。系统会自动替换这个app。
4)写一个工程来使用downloadprovider.
直接贴源码了:
downloadactivity.java
package com.xxxx.usedownload;
import java.io.filenotfoundexception;
import java.net.uri;
import android.app.activity;
import android.content.contentresolver;
import android.content.contentvalues;
import android.content.context;
import android.net.uri;
import android.os.bundle;
import android.webkit.urlutil;
/**
* @author lixinso
* 使用downloadprovider
*/
public class downloadactivity extends activity {
@override
public void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.main);
//string url = "http://192.168.200.76:8080/webserver/dancing-skeleton.3gp";
string contentdisposition = "attachment; filename=/"dancing-skeleton.3gp/"";
string mimetype = "video/3gpp";
string filename = urlutil.guessfilename(url,contentdisposition, mimetype);
uri uri = null;
try {
// undo the percent-encoding that kurl may have done.
string newurl = new string(urlutil.decode(url.getbytes()));
// parse the url into pieces
webaddress w = new webaddress(newurl);
string frag = null;
string query = null;
string path = w.mpath;
// break the path into path, query, and fragment
if (path.length() > 0) {
// strip the fragment
int idx = path.lastindexof('#');
if (idx != -1) {
frag = path.substring(idx + 1);
path = path.substring(0, idx);
}
idx = path.lastindexof('?');
query = path.substring(idx + 1);
}
uri = new uri(w.mscheme, w.mauthinfo, w.mhost, w.mport, path,
query, frag);
} catch (exception e) {
//log.e(logtag, "could not parse url for download: " + url, e);
return;
}
contentvalues values = new contentvalues();
values.put("uri", uri.tostring());
values.put("useragent", "mozilla/5.0 (linux; u; android 1.5; en-us; sdk build/cupcake) applewebkit/528.5+ (khtml, like gecko) version/3.1.2 mobile safari/525.20.1");
values.put("notificationpackage", getpackagename());
values.put("notificationclass", "helloworld");
values.put("visibility", 1);
values.put("mimetype", mimetype);
values.put("hint", filename);
values.put("description", uri.gethost());
values.put("total_bytes", 1349528);
values.put("destination", 1);
//这些参数参考:downloadprovider工程中的:helpers.java
//public static downloadfileinfo generatesavefile(
// context context,
// string url,
// string hint,
// string contentdisposition,
// string contentlocation,
// string mimetype,
// int destination,
// int contentlength) throws filenotfoundexception {
//以及: framework里的downloads.java;
contentresolver mresolver = getcontentresolver();
mresolver.insert(uri.parse("content://downloads/download"), values);
}
}
androidmanifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xxxx.usedownload"
android:versioncode="1"
android:versionname="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".downloadactivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.main" />
<category android:name="android.intent.category.launcher" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minsdkversion="7" />
<uses-permission android:name="android.permission.access_cache_filesystem" />
<uses-permission android:name="android.permission.receive_boot_completed" />
<uses-permission android:name="android.permission.access_download_manager" />
<uses-permission android:name="android.permission.access_download_manager_advanced" />
<uses-permission android:name="android.permission.access_drm" />
<uses-permission android:name="android.permission.send_download_completed_intents" />
<uses-permission android:name="android.permission.access_network_state" />
<uses-permission android:name="android.permission.internet" />
<uses-permission android:name="android.permission.write_external_storage" />
<uses-permission android:name="android.permission.install_drm" />
</manifest>
代码里面引用了parseexception和webaddress两个类,可以从android源代码里找到copy进来,在这里frameworks/base/core/java/android/net。
代码里面有几个地方比较重要的:
a) 通过往downloadprovider提供的contentprovider “content://downloads/download” 中插入数据就能触发downloadprovider的执行。
b) values.put("destination", 1); 是下载文件存储在什么地方, 如果没有这个参数,默认保存在sdcard的download 下面 (constants.java 中的 default_dl_subdir = "/download" )
如果指定为1,是往内存的 /cache目录下存东西 (在/frameworks/base/core/java/android/provider/downloads.java中定义, public static final int destination_cache_partition = 1; )
b) 注意manifest中的一堆权限: access_download_manager是最基本的权限,这样可以使用downloadprovider下载。
如果需要destination=1,则需要 access_download_manager权限。(downloads.java中的注释 : all file types are allowed, and only the initiating
application can access the file (indirectly through a content provider). this requires the android.permission.access_download_manager_advanced permission.)
如果没有这个权限,在往 content://downloads/download插入的时候有权限问题报错:
09-16 17:16:38.062: error/databaseutils(763): writing exception to parcel
09-16 17:16:38.062: error/databaseutils(763): java.lang.securityexception: unauthorized destination code
09-16 17:16:38.062: error/databaseutils(763): at com.android.providers.downloads.downloadprovider.insert(downloadprovider.java:277)
09-16 17:16:38.062: error/databaseutils(763): at android.content.contentprovider$transport.insert(contentprovider.java:150)
09-16 17:16:38.062: error/databaseutils(763): at android.content.contentprovidernative.ontransact(contentprovidernative.java:140)
09-16 17:16:38.062: error/databaseutils(763): at android.os.binder.exectransact(binder.java:287)
09-16 17:16:38.062: error/databaseutils(763): at dalvik.system.nativestart.run(native method)
09-16 17:16:38.102: debug/androidruntime(4086): shutting down vm
因为downloadprovider.java中有这段代码:
if (dest != null) {
if (getcontext().checkcallingpermission(downloads.permission_access_advanced)
!= packagemanager.permission_granted
&& dest != downloads.destination_external
&& dest != downloads.destination_cache_partition_purgeable) {
throw new securityexception("unauthorized destination code");
所以:要往/cache目录下存东西,一定要记得这个权限哦。
实际运行起来,只加这个权限往/cache下存东西还不够,就又把其他一堆权限都加上了,具体哪些有用还没细看。
5) 将这个app直接以普通app安装上去,运行,可以看到下载成功到/cache里了。
第二种思路就是想办法获得system权限或者签名:
这样不修改downloadprovider的代码,不动它。
而是将自己编写的app做完以后放到/packages/app目录下和整个系统一起编译,将其编译到img中的系统app下 这样编译完成以后运行,使用编译的img运行模拟器。在模拟器中启动自己写的调用downloadprovider的app,发现竟然也是可以调用的。
不过这种方法在模拟器上成功了,但是在真机上没成功,可能还有些问题没解决。第一种方法是完全成功的。