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);
    ……
}

猜你喜欢

转载自blog.csdn.net/xl19862005/article/details/60581363