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,發現竟然也是可以調用的。
不過這種方法在模拟器上成功了,但是在真機上沒成功,可能還有些問題沒解決。第一種方法是完全成功的。