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

Commit 415ee62a authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Validate ringtone URIs before setting" into main

parents 4217c9a4 323fbda2
Loading
Loading
Loading
Loading
+2 −9
Original line number Diff line number Diff line
@@ -51,16 +51,9 @@ public class DefaultRingtonePreference extends RingtonePreference {
            return;
        }

        String mimeType = mUserContext.getContentResolver().getType(ringtoneUri);
        if (mimeType == null) {
        if (!isValidRingtoneUri(ringtoneUri)) {
            Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
                    + " ignored: failure to find mimeType (no access from this context?)");
            return;
        }

        if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
            Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
                    + " ignored: associated mimeType:" + mimeType + " is not an audio type");
                    + " ignored: invalid ringtone Uri");
            return;
        }

+83 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.settings;

import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -23,9 +25,11 @@ import android.media.AudioAttributes;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.System;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;

import androidx.preference.Preference;
import androidx.preference.PreferenceManager;
@@ -239,4 +243,83 @@ public class RingtonePreference extends Preference {
        return true;
    }

    public boolean isDefaultRingtone(Uri ringtoneUri) {
        // null URIs are valid (None/silence)
        return ringtoneUri == null || RingtoneManager.isDefault(ringtoneUri);
    }

    protected boolean isValidRingtoneUri(Uri ringtoneUri) {
        if (isDefaultRingtone(ringtoneUri)) {
            return true;
        }

        // Return early for android resource URIs
        if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(ringtoneUri.getScheme())) {
            return true;
        }

        String mimeType = mUserContext.getContentResolver().getType(ringtoneUri);
        if (mimeType == null) {
            Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
                    + " failed: failure to find mimeType (no access from this context?)");
            return false;
        }

        if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg")
                || mimeType.equals("application/x-flac"))) {
            Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
                    + " failed: associated mimeType:" + mimeType + " is not an audio type");
            return false;
        }

        // Validate userId from URIs: content://{userId}@...
        final int userIdFromUri = ContentProvider.getUserIdFromUri(ringtoneUri, mUserId);
        if (userIdFromUri != mUserId) {
            final UserManager userManager = mUserContext.getSystemService(UserManager.class);

            if (!userManager.isSameProfileGroup(mUserId, userIdFromUri)) {
                Log.e(TAG,
                    "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + userIdFromUri
                        + " and user " + mUserId + " are not in the same profile group");
                return false;
            }

            final int parentUserId;
            final int profileUserId;
            if (userManager.isProfile()) {
                profileUserId = mUserId;
                parentUserId = userIdFromUri;
            } else {
                parentUserId = mUserId;
                profileUserId = userIdFromUri;
            }

            final UserHandle parent = userManager.getProfileParent(UserHandle.of(profileUserId));
            if (parent == null || parent.getIdentifier() != parentUserId) {
                Log.e(TAG,
                    "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + profileUserId
                        + " is not a profile of user " + parentUserId);
                return false;
            }

            // Allow parent <-> managed profile sharing, unless restricted
            if (userManager.hasUserRestrictionForUser(
                UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, UserHandle.of(parentUserId))) {
                Log.e(TAG,
                    "isValidRingtoneUri for URI:" + ringtoneUri + " failed: user " + parentUserId
                        + " has restriction: " + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE);
                return false;
            }

            if (!(userManager.isManagedProfile(profileUserId) || userManager.getUserProperties(
                    UserHandle.of(profileUserId)).isMediaSharedWithParent())) {
                Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
                    + " failed: user " + profileUserId + " is not a cloned or managed profile");
                return false;
            }
        }

        return true;
    }

}
+10 −2
Original line number Diff line number Diff line
@@ -25,10 +25,13 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.util.AttributeSet;

import android.util.Log;
import com.android.settings.R;
import com.android.settings.RingtonePreference;

public class NotificationSoundPreference extends RingtonePreference {
    private static final String TAG = "NotificationSoundPreference";

    private Uri mRingtone;

    public NotificationSoundPreference(Context context, AttributeSet attrs) {
@@ -50,8 +53,13 @@ public class NotificationSoundPreference extends RingtonePreference {
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
        if (data != null) {
            Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
            if (isValidRingtoneUri(uri)) {
                setRingtone(uri);
                callChangeListener(uri);
            } else {
                Log.e(TAG, "onActivityResult for URI:" + uri
                    + " ignored: invalid ringtone Uri");
            }
        }

        return true;
+77 −4
Original line number Diff line number Diff line
@@ -16,16 +16,20 @@

package com.android.settings;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.ContentInterface;
import android.content.ContentResolver;
import android.content.Context;
import android.media.RingtoneManager;
import android.content.pm.UserProperties;
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;

import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -34,17 +38,22 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

/** Unittest for DefaultRingtonePreference. */
@RunWith(AndroidJUnit4.class)
public class DefaultRingtonePreferenceTest {

    private static final int OWNER_USER_ID = 1;
    private static final int OTHER_USER_ID = 10;
    private static final int INVALID_RINGTONE_TYPE = 0;
    private DefaultRingtonePreference mDefaultRingtonePreference;

    @Mock
    private ContentResolver mContentResolver;
    @Mock
    private UserManager mUserManager;
    private Uri mRingtoneUri;

    @Before
@@ -52,14 +61,29 @@ public class DefaultRingtonePreferenceTest {
        MockitoAnnotations.initMocks(this);

        Context context = spy(ApplicationProvider.getApplicationContext());
        doReturn(mContentResolver).when(context).getContentResolver();
        mContentResolver = ContentResolver.wrap(Mockito.mock(ContentInterface.class));
        when(context.getContentResolver()).thenReturn(mContentResolver);

        mDefaultRingtonePreference = spy(new DefaultRingtonePreference(context, null /* attrs */));
        doReturn(context).when(mDefaultRingtonePreference).getContext();

        // Use INVALID_RINGTONE_TYPE to return early in RingtoneManager.setActualDefaultRingtoneUri
        when(mDefaultRingtonePreference.getRingtoneType())
                .thenReturn(RingtoneManager.TYPE_RINGTONE);
        mDefaultRingtonePreference.setUserId(1);
                .thenReturn(INVALID_RINGTONE_TYPE);

        mDefaultRingtonePreference.setUserId(OWNER_USER_ID);
        mDefaultRingtonePreference.mUserContext = context;
        when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(false);

        when(context.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
        when(context.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);

        UserProperties userProperties = new UserProperties.Builder().setMediaSharedWithParent(false)
                .build();
        when(mUserManager.getUserProperties(UserHandle.of(OTHER_USER_ID))).thenReturn(
                userProperties);

        mRingtoneUri = Uri.parse("content://none");
    }

    @Test
@@ -79,4 +103,53 @@ public class DefaultRingtonePreferenceTest {

        verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_notManagedProfile_shouldNotSetRingtone() {
        mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
        when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
        when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true);
        when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn(
                UserHandle.of(OWNER_USER_ID));
        when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(false);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_notSameUser_shouldNotSetRingtone() {
        mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
        when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
        when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(false);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_isManagedProfile_shouldSetRingtone() {
        mRingtoneUri = Uri.parse("content://" + OTHER_USER_ID + "@ringtone");
        when(mContentResolver.getType(mRingtoneUri)).thenReturn("audio/*");
        when(mUserManager.isSameProfileGroup(OWNER_USER_ID, OTHER_USER_ID)).thenReturn(true);
        when(mUserManager.getProfileParent(UserHandle.of(OTHER_USER_ID))).thenReturn(
                UserHandle.of(OWNER_USER_ID));
        when(mUserManager.isManagedProfile(OTHER_USER_ID)).thenReturn(true);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri);
    }

    @Test
    public void onSaveRingtone_defaultUri_shouldSetRingtone() {
        mRingtoneUri = Uri.parse("default_ringtone");
        when(mDefaultRingtonePreference.isDefaultRingtone(any(Uri.class))).thenReturn(true);

        mDefaultRingtonePreference.onSaveRingtone(mRingtoneUri);

        verify(mDefaultRingtonePreference).setActualDefaultRingtoneUri(mRingtoneUri);
    }
}