如何获得所有的SD卡,采用了新的棒棒糖API? [英] How to get access to all SD-cards, using the new Lollipop API?

查看:649
本文介绍了如何获得所有的SD卡,采用了新的棒棒糖API?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

棒棒糖开始,应用程序可以访问实时的SD卡(以后是不是奇巧访问,并没有正式支持尚未制作previous版本),因为我问的here

问题

由于它已成为相当罕见地看到,支持SD卡,并因为模拟器并没有真正有能力(或不是吗?)来模拟SD卡的支持棒棒糖设备,我花了相当长的一段对其进行测试。

无论如何,这似乎而是采用了普通的文件类访问SD卡(一旦你得到了它的权限),你需要使用URI它,使用DocumentFile。

这限制了进入正常的路径,因为我无法找到一种方法,这些URI转换为路径,反之亦然(再加上它是很烦人)。这也意味着,我不知道如何检查,如果当前的SD卡/ s的访问,所以我不知道什么时候来询问用户是否允许读/写它(或它们)。

我已经试过

目前,这是我得到的路径给所有SD卡:

  @TargetApi(Build.VERSION_ codeS.HONEYCOMB)
公共静态列表<字符串> getSdCardPaths(上下文的背景下,最终布尔includePrimaryExternalStorage)
  {
  最终的文件[] externalCacheDirs = ContextCompat.getExternalCacheDirs(上下文);
  如果(externalCacheDirs == NULL || externalCacheDirs.length == 0)
    返回null;
  如果(externalCacheDirs.length == 1)
    {
    如果(externalCacheDirs [0] == NULL)
      返回null;
    最后弦乐storageState = EnvironmentCompat.getStorageState(externalCacheDirs [0]);
    如果(!Environment.MEDIA_MOUNTED.equals(storageState))
      返回null;
    if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_$c$cS.HONEYCOMB&&Environment.isExternalStorageEmulated())
      返回null;
    }
  最后的名单,其中,字符串>结果=新的ArrayList<>();
  如果(includePrimaryExternalStorage || externalCacheDirs.length == 1)
    result.add(getRootOfInnerSdCardFolder(externalCacheDirs [0]));
  的for(int i = 1; I< externalCacheDirs.length ++ I)
    {
    最终文件文件= externalCacheDirs [I]
    如果(文件== NULL)
      继续;
    最后弦乐storageState = EnvironmentCompat.getStorageState(文件);
    如果(Environment.MEDIA_MOUNTED.equals(storageState))
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs [I]));
    }
  如果(result.isEmpty())
    返回null;
  返回结果;
  }

私有静态字符串getRootOfInnerSdCardFolder(档案文件)
  {
  如果(文件== NULL)
    返回null;
  最终长totalSpace = file.getTotalSpace();
  而(真)
    {
    最后文件par​​entFile = file.getParentFile();
    如果(parentFile == NULL || parentFile.getTotalSpace()!= totalSpace)
      返回file.getAbsolutePath();
    文件= parentFile;
    }
  }
 

这是我如何检查什么尤里斯我能达到:

 最后的名单,其中,UriPermission> persistedUriPermissions = getContentResolver()getPersistedUriPermissions()。
 

这是如何获得访问SD卡:

  startActivityForResult(新意图(Intent.ACTION_OPEN_DOCUMENT_TREE),42);

公共无效onActivityResult(INT申请code,INT结果code,意图resultData)
  {
  如果(结果code!= RESULT_OK)
    返回;
  乌里treeUri = resultData.getData();
  DocumentFile pickedDir = DocumentFile.fromTreeUri(这一点,treeUri);
  grantUriPermission(getPackageName(),treeUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  getContentResolver().takePersistableUriPermission(treeUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  }
 

的问题

  1. 是否有可能检查当前的SD卡都可以访问,哪些不是,并以某种方式要求用户获得许可,他们呢?

  2. 有没有到DocumentFile URI和实际路径之间转换的正式的方式?我发现 这个答案 ,但它在我的情况下崩溃了,再加上它看起来hack-年。

  3. 是否有可能从有关特定路径的用户请求的权限?甚至表现出的只是对话你们接受是/否?

  4. 是否有可能使用DocumentFile API的正常文件API,而不是一旦许可被授予?

  5. 由于文件/文件路径,是有可能只是请求的权限来访问它(并检查它是否之前给出),或它的根路径?

  6. 是否有可能使模拟器有一个SD卡?目前,它有SD卡被提及,但它的作品作为主要外部存储,我想要使用辅助外部存储进行测试,以尝试并使用新的API。

我认为,其中一些问题,一个有很大帮助回答对方。

解决方案
  

时可以检查当前的SD卡都可以访问,哪些不是,并以某种方式要求用户获得许可,他们呢?

有3个部分,以这样的:检测,什么卡还有,如果安装在卡检查,并要求访问

如果设备制造商都是好人,就可以得到外部存储目录通过调用<一href="https://developer.android.com/reference/android/content/Context.html#getExternalFilesDirs%28java.lang.String%29"相对=nofollow> getExternalFiles 中与参数(请参阅相关的Javadoc)。

他们可能不是好人。而且不是每个人都有最新,最热,无缺陷的操作系统版本与定期更新。所以可能有一些目录,未列出的存在(如OTG USB存储器等)如有任何疑问,你可以得到OS坐骑的完整列表,从文件的/ proc /坐骑。这个文件是Linux内核的一部分,它的格式是记录这里。您可以使用内容的/ proc /坐骑备份:解析,找到可用的文件系统(FAT,EXT3,EXT4等),并删除重复与输出 getExternalFilesDirs 。提供其余选项为用户下的一些非强制性的名字,像其它目录。这会给你一切可能的外部,内部,无论存储过。

您可以的天真地的检查,如果目录是可读/写调用的的CanRead 和的 canWrite 就可以了。如果这些成功,在做额外的工作没有意义。如果没有,你要么有一个持续开放的权限或没有。

获得许可是一个丑陋的一部分。 AFAIK,有没有办法做到这一点cleanely内SAF基础设施。 Intent.ACTION_PICK 听起来像一些的可以的工作(因为它接受一个开放的,从中挑),但事实并非如此。也许,这可以被认为是一个错误,应当报的Andr​​oid bug跟踪系统本身。

  

由于文件/文件路径,是有可能只是请求的权限来访问它(并检查它是否之前给出),或它的根路径?

这是什么 ACTION_PICK 是。同样,SAF选择器不支持 ACTION_PICK 开箱。第三方文件管理器可以,但很少有人会真正授予您真正进入。您可能会报告该错误为过,如果你喜欢它的感觉。

  

有没有到DocumentFile URI和实际路径之间转换的正式的方式?我发现这个答案,但它在我的情况下崩溃了,再加上它看起来黑客-Y。

没有。决不。你必永远是屈从于存储访问架构创作者的冲动! 邪恶的笑声

其实,还有一个更简单的方法:只需打开URI和检查创建描述符文件系统位置(为简单起见,棒棒糖版本。):

 公共字符串getFilesystemPath(上下文的背景下,开放的URI){
  ContentResolver的解析度= context.getContentResolver();

  字符串解决;
  尝试(ParcelFileDescriptor FD = res.openFileDescriptor(someSafUri,R)){
    最终文件procfsFdFile =新的文件(的/ proc /自/ FD /+ fd.getFd());

    解决= Os.readlink(procfsFdFile.getAbsolutePath());

    如果(TextUtils.isEmpty(解决)
          || resolved.charAt(0)!='/'
          || resolved.startsWith(的/ proc /)
          || resolved.startsWith(/ FD /))
    返回null;
  }赶上(例外errnoe){
    返回null;
  }
}
 

如果上面的方法返回一个位置,你仍然需要访问使用它的文件。如果它不返回一个位置,开放的问题并不是指文件(即使是暂时的)。它可能是一个网络数据流,UNIX管道,等等。你可以得到的版本方法上面从这个答案较旧的Andr​​oid版本。它的工作原理与的任意的开放的,任何ContentProvider的,不只是SAF-只要开放的我们可以打开 openFileDescriptor (例如,它是从 Intent.CATEGORY_OPENABLE )。

请注意,上面的方法可以被认为是官方的,如果你认为是这样的任何部分官方的Linux API的。许多Linux软件使用它,而且我也看到它被某些AOSP code(如Launcher3测试)。

  

是否有可能使用DocumentFile API的正常文件API,而不是一旦许可被授予?

您不能。不就棒棒糖最少。普通文件API进入Linux内核的权限。根据Linux内核,你的外置SD卡具有限制性的权限,即$ P $使用它pvent您的应用程序。权限,由存储访问架构给出了由SAF管理(IIRC存储在某些的 XML文件的)和内核知道见死不救。你必须使用中间方(存储访问架构)来访问外部存储。请注意,Linux内核有它自己的机制来管理访问目录子树(称为约束力坐骑的),但存储访问架构的创造者要么不知道,或不想使用它。

您可以得到一定程度的访问(pretty的任何东西,可与文件来完成),通过使用文件描述符,从开放的创建。我建议您阅读这个答案,它可能对使用文件desriptors和一般相对于Android的一些有用的信息。

background

Starting with Lollipop, apps can get access to real SD-cards (after it was not accessible on Kitkat, and was not officially supported yet worked on previous versions), as I've asked about here.

The problem

Because it has become quite rare to see a Lollipop device that supports SD-card and because the emulator doesn't really has the ability (or does it?) to emulate an SD-card support, it took me quite a while to test it.

Anyway, it seems that instead of using the normal File classes to access the SD-card (once you got a permission for it), you need to use Uris for it, using DocumentFile .

This limits the access to the normal paths, as I can't find a way to convert the Uris to paths and vice versa (plus it's quite annoying). It also means that I don't know how to check if the current SD-card/s are accessible, so I don't know when to ask the user for permission to read/write to it (or to them).

What I've tried

Currently, this is how I get the paths to all SD-cards:

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static List<String> getSdCardPaths(Context context,final boolean includePrimaryExternalStorage)
  {
  final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
  if(externalCacheDirs==null||externalCacheDirs.length==0)
    return null;
  if(externalCacheDirs.length==1)
    {
    if(externalCacheDirs[0]==null)
      return null;
    final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
    if(!Environment.MEDIA_MOUNTED.equals(storageState))
      return null;
    if(!includePrimaryExternalStorage&&VERSION.SDK_INT>=VERSION_CODES.HONEYCOMB&&Environment.isExternalStorageEmulated())
      return null;
    }
  final List<String> result=new ArrayList<>();
  if(includePrimaryExternalStorage||externalCacheDirs.length==1)
    result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0]));
  for(int i=1;i<externalCacheDirs.length;++i)
    {
    final File file=externalCacheDirs[i];
    if(file==null)
      continue;
    final String storageState=EnvironmentCompat.getStorageState(file);
    if(Environment.MEDIA_MOUNTED.equals(storageState))
      result.add(getRootOfInnerSdCardFolder(externalCacheDirs[i]));
    }
  if(result.isEmpty())
    return null;
  return result;
  }

private static String getRootOfInnerSdCardFolder(File file)
  {
  if(file==null)
    return null;
  final long totalSpace=file.getTotalSpace();
  while(true)
    {
    final File parentFile=file.getParentFile();
    if(parentFile==null||parentFile.getTotalSpace()!=totalSpace)
      return file.getAbsolutePath();
    file=parentFile;
    }
  }

This is how I check what Uris I can reach:

final List<UriPermission> persistedUriPermissions=getContentResolver().getPersistedUriPermissions();

This is how to get access to the SD cards:

startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE),42);

public void onActivityResult(int requestCode,int resultCode,Intent resultData)
  {
  if(resultCode!=RESULT_OK)
    return;
  Uri treeUri=resultData.getData();
  DocumentFile pickedDir=DocumentFile.fromTreeUri(this,treeUri);
  grantUriPermission(getPackageName(),treeUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  getContentResolver().takePersistableUriPermission(treeUri,Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  }

The questions

  1. Is it possible to check if the current SD-cards are accessible, which aren't , and somehow ask the user to get permission to them?

  2. Is there an official way to convert between the DocumentFile uris and real paths? I've found this answer, but it crashed in my case, plus it looks hack-y.

  3. Is it possible to request permission from the user about a specific path? Maybe even show just the dialog of "do you accept yes/no ? " ?

  4. Is it possible to use the normal File API instead of the DocumentFile API once the permission was granted?

  5. Given a file/filepath, is it possible to just request a permission to access it (and check if it's given before), or its root path ?

  6. Is it possible to make the emulator have an SD-card? Currently, it has "SD-card" being mentioned, but it works as the primary external storage, and I'd like to test it using the secondary external storage, in order to try and use the new API.

I think that for some of those questions, one helps a lot to answer the other.

解决方案

Is it possible to check if the current SD-cards are accessible, which aren't , and somehow ask the user to get permission to them?

There are 3 parts to this: detecting, what cards there are, checking if a card is mounted, and asking for access.

If device manufacturers are good guys, you can get list of "external" storage by calling getExternalFiles with null argument (see the associated javadoc).

They may not be good guys. And not everyone have newest, hottest, bug-free OS version with regular updates. So there may be some directories, that aren't listed there (such as OTG USB storage etc.) Whenever in doubt, you can get full list of OS mounts from file /proc/mounts. This file is part of Linux kernel, it's format is documented here. You can use contents of /proc/mounts as backup: parse it, find usable filesystems (fat, ext3, ext4 etc.) and remove duplicates with output of getExternalFilesDirs. Offer remaining options to users under some non-obtrusive name, something like "misc directories". That will give you every possible external, internal, whatever storage ever.

You can naively check, if the directory is readable/writable by calling canRead and canWrite on it. If these succeeds, no point in doing extra work. If it does not, you either have a persisted Uri permission or don't.

"Getting permission" is an ugly part. AFAIK, there is no way to do that cleanely within SAF infrastructure. Intent.ACTION_PICK sounds like something that may work (since it accepts an Uri, from which to pick), but it does not. Maybe, this can be considered a bug and should be reported to Android bug tracker as such.

Given a file/filepath, is it possible to just request a permission to access it (and check if it's given before), or its root path ?

This is what ACTION_PICK is for. Again, the SAF picker does not support ACTION_PICK out of box. Third-party file managers may, but very few of them will actually grant you real access. You may report this as bug too, if you feel like it.

Is there an official way to convert between the DocumentFile uris and real paths? I've found this answer, but it crashed in my case, plus it looks hack-y.

No. Never. You shalt forever remain subservient to whims of Storage Access Framework creators! evil laughter

Actually, there is a much simpler way: just open the Uri and check filesystem location of created descriptor (for simplicity, Lollipop-only version):

public String getFilesystemPath(Context context, Uri uri) {
  ContentResolver res = context.getContentResolver();

  String resolved;
  try (ParcelFileDescriptor fd = res.openFileDescriptor(someSafUri, "r")) {
    final File procfsFdFile = new File("/proc/self/fd/" + fd.getFd());

    resolved = Os.readlink(procfsFdFile.getAbsolutePath());

    if (TextUtils.isEmpty(resolved)
          || resolved.charAt(0) != '/'
          || resolved.startsWith("/proc/")
          || resolved.startsWith("/fd/"))
    return null;
  } catch (Exception errnoe) {
    return null;
  }
}

If the method above returns a location, you still need access to use it with File. If it does not return a location, the Uri in question does not refer to file (even temporary one). It may be a network stream, Unix pipe, whatever. You can get version of method above for older Android versions from this answer. It works with any Uri, any ContentProvider—not just SAF—as long as the Uri can be opened with openFileDescriptor (e.g. it is from Intent.CATEGORY_OPENABLE).

Note, that approach above can be considered official, if you consider any part of official Linux API as such. A lot of Linux software uses it, and I have also seen it to be used by some AOSP code (such as in Launcher3 tests).

Is it possible to use the normal File API instead of the DocumentFile API once the permission was granted?

You can not. Not on Lollipop at least. Normal file API goes to Linux kernel for permissions. According to Linux kernel, your external SD card has restrictive permissions, that prevent your app from using it. The permissions, given by Storage Access Framework are managed by SAF (IIRC stored in some xml files) and kernel knows nothing about them. You have to use intermediate party (Storage Access Framework) to gain access to external storage. Note, that Linux kernel has it's own mechanism for managing access to directory subtrees (it is called bind-mounts), but Storage Access Framework creators either don't know about it or don't want to use it.

You can get some degree of access (pretty much anything, that can be done with File) by using file descriptor, created from Uri. I recommend you to read this answer, it may have some helpful information about using file desriptors in general and in relation to Android.

这篇关于如何获得所有的SD卡,采用了新的棒棒糖API?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆