Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 783ee0ca authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Ask RingtonePlayer to open data for caching.

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
parent a3c1c229
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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);
}
+35 −9
Original line number Diff line number Diff line
@@ -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,8 +225,8 @@ 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;

@@ -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;
+35 −0
Original line number Diff line number Diff line
@@ -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) {