天天看點

使用Android内部的DownloadProvider下載下傳檔案,并擷取cache權限

 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,發現竟然也是可以調用的。

不過這種方法在模拟器上成功了,但是在真機上沒成功,可能還有些問題沒解決。第一種方法是完全成功的。