1. 程式人生 > >android mtp模式下連線PC後只顯示指定資料夾

android mtp模式下連線PC後只顯示指定資料夾

轉載請註明文章出錯及作者
作者:Xandy
出處:http://blog.csdn.net/xl19862005
一、mtp概述
android在3.0以後的版本加入了mtp的支援,相對於mass storage模式,由於mtp優越性,現在幾乎所有的手機連線PC後都是以mtp的方式進行檔案訪問。
這裡簡單講述一下mtp的優點:
1、Initiator和Responder可同時對檔案進行儲存。相對於mass storage的模式,這種優點是顯而易見的。
這裡寫圖片描述
PC連線上responder裝置之後,不是直接對裝置中的儲存分割槽進行訪問,而是通過vfs的方式間接訪問儲存分割槽中的檔案,這個作為Initiator端的PC裝置來說,就不再需要關心要訪問的responder儲存分割槽是什麼檔案系統了,通過公用的vfs就可以對不同檔案系統的儲存裝置進行讀寫了。
2、mtp模式下Initiator可以知道Responder所支援的媒體檔案格式有哪些
3、檔案訪問許可權可控。這點是筆者根據android下mtp的架構自加的,也正是本文所需要說的重點。
二、android mtp啟動流程
這裡寫圖片描述


這裡要提到一點的是:android裝置啟動之後,當在MediaScannerReceiver(android_src/providers/MediaProvider/src/com/android/providers/media/MediaScannerReceiver.java)中監聽到開機完成廣播(android.intent.action.BOOT_COMPLETED)時,會啟動MeidaScannerService,對整個裝置內部的儲存裝置進行掃描,並將掃描到的檔案存入資料庫!
這裡要提一個原生android系統的bug:在開機完成之後,在android裝置上拍照或者截圖後,將裝置連線上PC,是無法找到剛拍的照片或截圖圖片的!這是因為MediaScannerService的啟動只在BOOT_COMPLETED時scan一次,此後新增加的檔案都還沒有更新到資料庫,需要重啟系統後在PC上才能發現新增加的檔案。
為了解決此bug,我在MediaScannerReceiver中增加了對USB_STATE狀態廣播的監聽,所以每次插拔USB時都會scan一次。
而從上圖可知,MtpService啟動後也是需要去資料庫(MtpDatabase)裡拿檔案的,所以可以通過修改資料庫的查詢規則來達到連線PC後只顯示指定檔案/資料夾的功能。
三、檔案過濾程式碼修改
首先來看看MtpDatabase(android_src/frameworks/base/media/java/android/mtp/MtpDatabase.java)裡關於建立資料庫查詢的方法:

private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
        String where;
        String[] whereArgs;

        if (storageID == 0xFFFFFFFF) {
            // query all stores
            if (format == 0) {
                // query all formats
                if
(parent == 0) { // query all objects where = null; whereArgs = null; } else { if (parent == 0xFFFFFFFF) { // all objects in root of store parent = 0; } where = PARENT_WHERE; whereArgs = new String[] { Integer.toString(parent) }; } } else { // query specific format if (parent == 0) { // query all objects where = FORMAT_WHERE; whereArgs = new String[] { Integer.toString(format) }; } else { if (parent == 0xFFFFFFFF) { // all objects in root of store parent = 0; } where = FORMAT_PARENT_WHERE; whereArgs = new String[] { Integer.toString(format), Integer.toString(parent) }; } } } else { // query specific store if (format == 0) { // query all formats if (parent == 0) { // query all objects where = STORAGE_WHERE; whereArgs = new String[] { Integer.toString(storageID) }; } else { if (parent == 0xFFFFFFFF) { // all objects in root of store parent = 0; where = STORAGE_PARENT_WHERE; whereArgs = new String[] { Integer.toString(storageID), Integer.toString(parent) }; } } else { // query specific format if (parent == 0) { // query all objects where = STORAGE_FORMAT_WHERE; whereArgs = new String[] { Integer.toString(storageID), Integer.toString(format) }; } else { if (parent == 0xFFFFFFFF) { // all objects in root of store parent = 0; } where = STORAGE_FORMAT_PARENT_WHERE; whereArgs = new String[] { Integer.toString(storageID), Integer.toString(format), Integer.toString(parent) }; } } } // if we are restricting queries to mSubDirectories, we need to add the restriction // onto our "where" arguments if (mSubDirectoriesWhere != null) { if (where == null) { where = mSubDirectoriesWhere; whereArgs = mSubDirectoriesWhereArgs; } else { where = where + " AND " + mSubDirectoriesWhere; // create new array to hold whereArgs and mSubDirectoriesWhereArgs String[] newWhereArgs = new String[whereArgs.length + mSubDirectoriesWhereArgs.length]; int i, j; for (i = 0; i < whereArgs.length; i++) { newWhereArgs[i] = whereArgs[i]; } for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) { newWhereArgs[i] = mSubDirectoriesWhereArgs[j]; } whereArgs = newWhereArgs; } } return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where, whereArgs, null, null); }

而方法query的定義如下:

public Cursor query(String callingPkg, Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder, IcancellationSignal cancellationSignal)throws RemoteExceptioin;

不難理解,方法createObjectQuery中獲得的where 字串是用於到資料庫裡查詢與where匹配的檔案的,這裡還要說明一點的是mSubDirectoriersWhere,可以看到當這個subDirectoriersWhere不為空時,作了如下合併到where字串

where = where + " AND " + mSubDirectoriesWhere;

那麼可以通過增加對這個mSubDirectoriesWhere的賦值(賦以指定需要到資料庫裡查詢的資料夾名),就可以達到想要的檔案過濾功能!
而mSubDirectoriesWhere只在MtpDatabase的構造方法裡進行賦值:

public MtpDatabase(Context context, Stirng volumeName, String storagePath, String[] subDirectories){
      ……
      if(subDirectories != null){
            StringBuilder builder = new StringBuilder();
            int count = subDirectories.lenght;
            for(int i=0;i<count;i++){
                  builder.append..........
                  ……
            }
            ……
            mSubDirectoriesWhere = builer.toString();
      }
}

那麼只要在建立 MtpDatabase的時候傳入這個subDirectories String陣列就可以達到想要的功能了!
在MtpService(android_src/packages/providers/MediaProvider/src/com/android/providers/media/MtpService.java)中增加如下程式碼:

private static final String[] MTP_DIRECTORIES_PRIVATE = new String[]{
    Environment.DIRECTORY_DCIM,
    Environment.DIRECTORY_PICTURES,
    Environment.DIRECTORY_MOVIES,
    Environment.XXXXXX,
    ……
};
……
@Override
public int onStartCommand(Intent intent, int flags, int startId){
    ……
    if(!mPtpMode){
        int num = MTP_DIRECTORIES_PRIVATE.length;
        subdirs = new String[num];
        for(int j=0;j<num;j++){
            File file = Environment.getExternalStoragePublicDirectory(MTP_DIRECTORIES_PRIVATE[j]);
            file.mkdirs();
            subdirs[j] = file.getPath();
        }
    }
    ……
    //最後在這裡建立MtpDatabase,並傳入subdirs到前面提到的where
    mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, primary.getPath(), subdirs);
    ……
}