我的一些用户在尝试在我的应用中选择铃声时向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
获取铃声,并在String
和Uri
之间进行转换。没有其他代码触及用户的媒体。
此外,我没有行号,因为堆栈跟踪来自混淆代码,并且重新映射堆栈跟踪没有提供行号。
答案 0 :(得分:3)
遇到了同样的问题并提出了以下解决方案:
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));
答案 1 :(得分:1)
您可以使用WRITE_EXTERNAL_STORAGE
找到外部存储目录而无需READ_EXTERNAL_STORAGE
或Environment.getExternalStorageDirectory()
。
然后,您可以将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;
}
答案 2 :(得分:1)
以前,我使用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();
}