2014-01-17 32 views
3

我的一些用户在尝试在我的应用中选择铃声时向Google Play报告了以下错误。 (还有更多,但不相关)MediaProvider/Ring铃声的外部存储权限问题

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之间转换。没有其他代码正在触摸用户的媒体。

此外,我没有行号,因为stacktrace来自模糊代码,并且重新映射堆栈跟踪没有提供行号。

+0

你可以发布您的代码? –

回答

2

刚把相同问题并提出以下解决方案:

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

得到你需要的INTERNAL_CONTENT_URI_ID列值相结合的铃声的URI,您可以通过使用ContentUris类做到这一点:

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

我还没有测试过,但看起来好像会起作用。 – Jakar

+0

我已经在4.4.4中对它进行了测试,并且对照4.0.3的源代码进行了检查,因此它应该能够使用API​​ 15及更高版本 – atomicode

1

您可以通过使用Environment.getExternalStorageDirectory()找到不需要WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE的外部存储目录。

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

然后,不是将原始Cursor传递给用户界面,而是可以用List代替ListAdapter

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

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; 
} 
+0

我认为OP特别想知道这是否可以通过使用'RingtoneManager'来完成 – Magnus

+0

'RingtoneManager'只是让你查询各种不同的媒体提供者,以及从get URIs或者Ringtone对象本身。 我建议他利用'RingtoneManager'来执行初始查询,然后在请求'Ringtone'对象之前过滤结果,这样他就不会在外部存储器上发出对'Ringtone'对象的请求。 编辑我的原始答案,使之更清晰。 –

+0

当然是的,但是如果你可以在你挑选某物之前过滤*,那么整个目的(和更清洁的解决方案)就是。让他们选择一个铃声然后吐出一条信息说:“遗憾的是在SD卡上,请选择一个新的!”,这对用户来说是非常不友好的。 – Magnus

1

以前,我用RingtoneManager得到一个列表,并显示在对话框供用户选择。它被扔在SecurityExceptionringtoneManager.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();                          
}