diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl index 809142125031d..4b1e39f2fc9d1 100644 --- a/media/java/android/media/IRingtonePlayer.aidl +++ b/media/java/android/media/IRingtonePlayer.aidl @@ -18,6 +18,7 @@ package android.media; import android.media.AudioAttributes; import android.net.Uri; +import android.os.ParcelFileDescriptor; import android.os.UserHandle; /** @@ -36,4 +37,6 @@ interface IRingtonePlayer { /** Return the title of the media. */ String getTitle(in Uri uri); + + ParcelFileDescriptor openRingtone(in Uri uri); } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index feb490de2c3e3..4977391ed5c02 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -30,7 +30,9 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.os.Environment; +import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.provider.MediaStore; import android.provider.Settings; import android.provider.Settings.System; @@ -223,9 +225,9 @@ public class RingtoneManager { */ public static final int URI_COLUMN_INDEX = 2; - private Activity mActivity; - private Context mContext; - + private final Activity mActivity; + private final Context mContext; + private Cursor mCursor; private int mType = TYPE_RINGTONE; @@ -246,7 +248,8 @@ public class RingtoneManager { * @param activity The activity used to get a managed cursor. */ public RingtoneManager(Activity activity) { - mContext = mActivity = activity; + mActivity = activity; + mContext = activity; setType(mType); } @@ -258,6 +261,7 @@ public class RingtoneManager { * @param context The context to used to get a cursor. */ public RingtoneManager(Context context) { + mActivity = null; mContext = context; setType(mType); } @@ -271,7 +275,6 @@ public class RingtoneManager { * @see #EXTRA_RINGTONE_TYPE */ public void setType(int type) { - if (mCursor != null) { throw new IllegalStateException( "Setting filter columns should be done before querying for ringtones."); @@ -656,18 +659,19 @@ public class RingtoneManager { * @see #getActualDefaultRingtoneUri(Context, int) */ public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) { + final ContentResolver resolver = context.getContentResolver(); + String setting = getSettingForType(type); if (setting == null) return; - Settings.System.putString(context.getContentResolver(), setting, + Settings.System.putString(resolver, setting, ringtoneUri != null ? ringtoneUri.toString() : null); // Stream selected ringtone into cache so it's available for playback // when CE storage is still locked if (ringtoneUri != null) { - final ContentResolver cr = context.getContentResolver(); final Uri cacheUri = getCacheForType(type); - try (InputStream in = cr.openInputStream(ringtoneUri); - OutputStream out = cr.openOutputStream(cacheUri)) { + try (InputStream in = openRingtone(context, ringtoneUri); + OutputStream out = resolver.openOutputStream(cacheUri)) { Streams.copy(in, out); } catch (IOException e) { Log.w(TAG, "Failed to cache ringtone: " + e); @@ -675,6 +679,28 @@ public class RingtoneManager { } } + /** + * Try opening the given ringtone locally first, but failover to + * {@link IRingtonePlayer} if we can't access it directly. Typically happens + * when process doesn't hold + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. + */ + private static InputStream openRingtone(Context context, Uri uri) throws IOException { + final ContentResolver resolver = context.getContentResolver(); + try { + return resolver.openInputStream(uri); + } catch (SecurityException | IOException e) { + Log.w(TAG, "Failed to open directly; attempting failover: " + e); + final IRingtonePlayer player = context.getSystemService(AudioManager.class) + .getRingtonePlayer(); + try { + return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri)); + } catch (Exception e2) { + throw new IOException(e2); + } + } + } + private static String getSettingForType(int type) { if ((type & TYPE_RINGTONE) != 0) { return Settings.System.RINGTONE; diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java index f39f3026f26b0..ab612dd93dd13 100644 --- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java @@ -16,8 +16,10 @@ package com.android.systemui.media; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; +import android.database.Cursor; import android.media.AudioAttributes; import android.media.IAudioService; import android.media.IRingtonePlayer; @@ -25,15 +27,20 @@ import android.media.Ringtone; import android.net.Uri; import android.os.Binder; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.provider.MediaStore; +import android.provider.MediaStore.Audio.AudioColumns; import android.util.Log; +import com.android.internal.util.Preconditions; import com.android.systemui.SystemUI; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; @@ -180,6 +187,34 @@ public class RingtonePlayer extends SystemUI { return Ringtone.getTitle(getContextForUser(user), uri, false /*followSettingsUri*/, false /*allowRemote*/); } + + @Override + public ParcelFileDescriptor openRingtone(Uri uri) { + final UserHandle user = Binder.getCallingUserHandle(); + final ContentResolver resolver = getContextForUser(user).getContentResolver(); + + // Only open the requested Uri if it's a well-known ringtone or + // other sound from the platform media store, otherwise this opens + // up arbitrary access to any file on external storage. + if (uri.toString().startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) { + try (Cursor c = resolver.query(uri, new String[] { + MediaStore.Audio.AudioColumns.IS_RINGTONE, + MediaStore.Audio.AudioColumns.IS_ALARM, + MediaStore.Audio.AudioColumns.IS_NOTIFICATION + }, null, null, null)) { + if (c.moveToFirst()) { + if (c.getInt(0) != 0 || c.getInt(1) != 0 || c.getInt(2) != 0) { + try { + return resolver.openFileDescriptor(uri, "r"); + } catch (IOException e) { + throw new SecurityException(e); + } + } + } + } + } + throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri); + } }; private Context getContextForUser(UserHandle user) {