OSDN Git Service

Ask RingtonePlayer to open data for caching.
authorJeff Sharkey <jsharkey@android.com>
Sun, 6 Mar 2016 00:23:28 +0000 (17:23 -0700)
committerJeff Sharkey <jsharkey@android.com>
Sun, 6 Mar 2016 00:23:32 +0000 (17:23 -0700)
When setting default ringtones, RingtoneManager now caches the
selected media for playback before the device is unlocked.  However,
this API hasn't historically required the caller to hold storage
permissions.

To keep this working, we attempt to delegate ringtone access over
through RingtonePlayer, which is what we do for playback.  However,
because we're caching the real ringtone bits now, we need to be much
more careful about the PFDs we're willing to return.  This change
requires that they be in external storage, and that they have the
ringtone/alarm/notification bit set.

Bug: 27366059
Change-Id: I59c2adc1d1250a3eac281f190f35a7cb3119967b

media/java/android/media/IRingtonePlayer.aidl
media/java/android/media/RingtoneManager.java
packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java

index 8091421..4b1e39f 100644 (file)
@@ -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);
 }
index feb490d..4977391 100644 (file)
@@ -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;
index f39f302..ab612dd 100644 (file)
 
 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) {