【问题标题】:External Storage Permission Issue with MediaProvider / Ring TonesMediaProvider / 铃声的外部存储权限问题
【发布时间】:2014-01-17 21:42:59
【问题描述】:

我的一些用户在尝试在我的应用中选择铃声时向 Google Play 报告了以下错误。 (还有更多,但不相关)

java.lang.SecurityException: Permission Denial: 
reading com.android.providers.media.MediaProvider 
uri content://media/external/audio/media 
from pid=5738, uid=10122 requires android.permission.READ_EXTERNAL_STORAGE

我认为这个问题是由于外部存储上的某些音调而发生的。我不想在我的应用程序中包含READ_EXTERNAL_STORAGE 权限,除非我绝对必须这样做。

有没有办法绕过这个问题,只排除外部存储上可能存在的任何音调?

注意:我收到带有RingtoneManager 的铃声并将它们在StringUri 之间转换。没有其他代码触及用户的媒体。

另外,我没有行号,因为堆栈跟踪来自混淆代码,并且重新映射堆栈跟踪没有提供行号。

【问题讨论】:

  • 你能发布你的代码吗?

标签: android ringtone android-permissions securityexception


【解决方案1】:

刚刚遇到同样的问题,想出了以下解决方案:

private Cursor createCursor()
{
    Uri uri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;

    String[] columns = new String[]
    {
        MediaStore.Audio.Media._ID,
        MediaStore.Audio.Media.TITLE,
        MediaStore.Audio.Media.TITLE_KEY
    };

    String filter = createBooleanFilter(MediaStore.Audio.AudioColumns.IS_ALARM);
    String order = MediaStore.Audio.Media.DEFAULT_SORT_ORDER;

    return getContext().getContentResolver().query(uri, columns, filter, null, order);
}

private String createBooleanFilter(String... columns)
{
    if(columns.length > 0)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for(int i = columns.length - 1; i > 0; i--)
        {
            sb.append(columns[i]).append("=1 or ");
        }
        sb.append(columns[0]);
        sb.append(")");
        return sb.toString();
    }
    return null;
}

要获取铃声的 Uri,您需要将 INTERNAL_CONTENT_URI_ID 列值结合起来,您可以使用 ContentUris 类来做到这一点:

Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, cursor.getLong(0));

【讨论】:

  • 我还没有测试过,但看起来它可以工作。
  • 我已经在 4.4.4 中对其进行了测试,并检查了 4.0.3 的来源,因此它应该适用于 API 15 及更高版本
【解决方案2】:

使用Environment.getExternalStorageDirectory(),无需WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE即可找到外部存储目录。

然后,您可以将 RingtoneManager 提供的 URI 的路径与此路径进行比较,以查看它们是否在外部存储上,如果是,则将这些项目添加到 List

然后,您可以将 ListListAdapter 一起使用,而不是将原始 Cursor 传递给 UI。

例如(未测试,您可能需要更改比较路径的方法):

class RingtoneDetails
{
    public String ID;
    public String Title;
    public Uri Uri;

    public RingtoneDetails(String id, String title, Uri uri)
    {
        ID = id;
        Title = title;
        Uri = uri;
    }
}

private List<RingtoneDetails> getNonExternalRingtones(RingtoneManager manager)
{
    List<RingtoneDetails> ringtones = new List<RingtoneDetails>();
    Cursor cursor = manager.getCursor();
    String extDir = Environment.getExternalStorageDirectory().getAbsolutePath();

    while (cursor.moveToNext()) 
    {
        String id = cursor.getString(cursor.getColumnIndex(RingtoneManager.ID_COLUMN_INDEX));
        String title = cursor.getString(cursor.getColumnIndex(RingtoneManager.TITLE_COLUMN_INDEX));
        Uri uri= cursor.getString(cursor.getColumnIndex(RingtoneManager.URI_COLUMN_INDEX));

        if(!uri.getPath().contains(extDir))
        {
            ringtones.add(new Ringtone(id, title, uri));
        }
    }

    return ringtones;
}

【讨论】:

  • 我认为 OP 特别想知道这是否可以通过使用 RingtoneManager
  • RingtoneManager 只允许您查询各种不同的媒体提供程序,并从获取 URI 或 Ringtone 对象本身。我建议他使用RingtoneManager 来执行初始查询,然后在请求Ringtone 对象之前过滤结果,这样他就不会在外部存储上请求Ringtone 对象。编辑了我的原始答案以使其更清楚。
  • 我想知道我是否可以使用 RingtoneManager 来排除外部存储上的音调,因此这些音调不在setSingleChoiceItems(cursor, position, c.getColumnName(RingtoneManager.TITLE_COLUMN_INDEX) 返回的列表中,还有一个简短的示例或有用的链接可以帮助我有了这个。
  • 编辑了我的回答(希望)涵盖你所追求的。
  • 我还没有测试过它,但看起来如果有什么能解决问题的话,那就是这个。谢谢。
【解决方案3】:

以前,我使用RingtoneManager 获取列表并在对话框中显示该列表供用户选择。它把SecurityException 扔到ringtoneManager.getCursor();

我不想添加外部存储权限,所以我转而做:

final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Select Ringtone");
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE,RingtoneManager.TYPE_ALL);
startActivityForResult( intent, RINGTONE_RESULT);

然后在onActivityResult

if (requestCode == RINGTONE_RESULT&&resultCode == RESULT_OK&&data!=null) {                                                                             
    try {                                                                                                              
        Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);                                  
        if (uri==null){                                                                                                
            setSilent(); //UI stuff in this method                                                                                              
        } else {                                                                            
            Ringtone ringtone = RingtoneManager.getRingtone(context, uri);                                             
            String name = ringtone.getTitle(context);                                                                  
            changeTone.setText(name); //changeTone is a button                                                                  
        }                                                                                                              
    } catch (SecurityException e){                                                                                     
        setSilent();                                                                                                   
        Toast.makeText(context, "Error. Tone on user storage. Select a different ringtone.", Toast.LENGTH_LONG).show();
    } catch (Exception e){                                                                                             
        setSilent();                                                                                                   
        Toast.makeText(context, "Unknown error. Select a different ringtone.", Toast.LENGTH_SHORT).show();             
    }                                                                                                                  
} else {                                                                                                               
    Toast.makeText(context, "Ringtone not selected. Tone set to silent.", Toast.LENGTH_SHORT).show();                  
        setSilent();                                                                                                    
}  

【讨论】:

  • 但是这一行仍然会导致安全异常:Ringtone ringtone = RingtoneManager.getRingtone(context, uri);
  • @behelit,这对我来说似乎很奇怪。 getRingtone 的文档没有提到 SecurityException。自从我做 Android 以来已经很久了,虽然我真的不知道我在说什么。
猜你喜欢
  • 2017-07-26
  • 2023-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多