Validate ringtone URIs before setting am: 7ba175eaeb

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/24956689

Change-Id: Id2d5f2423cacd7e4bb06fe46e1773b512a79fc29
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Valentin Iftime
2023-11-08 19:22:51 +00:00
committed by Automerger Merge Worker
4 changed files with 165 additions and 15 deletions

View File

@@ -51,16 +51,9 @@ public class DefaultRingtonePreference extends RingtonePreference {
return; return;
} }
String mimeType = mUserContext.getContentResolver().getType(ringtoneUri); if (!isValidRingtoneUri(ringtoneUri)) {
if (mimeType == null) {
Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri
+ " ignored: failure to find mimeType (no access from this context?)"); + " ignored: invalid ringtone Uri");
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");
return; return;
} }

View File

@@ -16,6 +16,8 @@
package com.android.settings; package com.android.settings;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.TypedArray; import android.content.res.TypedArray;
@@ -23,9 +25,11 @@ import android.media.AudioAttributes;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.System; import android.provider.Settings.System;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@@ -239,4 +243,82 @@ public class RingtonePreference extends Preference {
return true; 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)) {
Log.e(TAG, "isValidRingtoneUri for URI:" + ringtoneUri
+ " failed: user " + profileUserId + " is not a managed profile");
return false;
}
}
return true;
}
} }

View File

@@ -25,10 +25,13 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.RingtonePreference; import com.android.settings.RingtonePreference;
public class NotificationSoundPreference extends RingtonePreference { public class NotificationSoundPreference extends RingtonePreference {
private static final String TAG = "NotificationSoundPreference";
private Uri mRingtone; private Uri mRingtone;
public NotificationSoundPreference(Context context, AttributeSet attrs) { public NotificationSoundPreference(Context context, AttributeSet attrs) {
@@ -50,8 +53,13 @@ public class NotificationSoundPreference extends RingtonePreference {
public boolean onActivityResult(int requestCode, int resultCode, Intent data) { public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null) { if (data != null) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (isValidRingtoneUri(uri)) {
setRingtone(uri); setRingtone(uri);
callChangeListener(uri); callChangeListener(uri);
} else {
Log.e(TAG, "onActivityResult for URI:" + uri
+ " ignored: invalid ringtone Uri");
}
} }
return true; return true;

View File

@@ -16,16 +16,19 @@
package com.android.settings; package com.android.settings;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.ContentInterface;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -34,17 +37,22 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
/** Unittest for DefaultRingtonePreference. */ /** Unittest for DefaultRingtonePreference. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DefaultRingtonePreferenceTest { 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; private DefaultRingtonePreference mDefaultRingtonePreference;
@Mock @Mock
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
@Mock @Mock
private UserManager mUserManager;
private Uri mRingtoneUri; private Uri mRingtoneUri;
@Before @Before
@@ -52,14 +60,24 @@ public class DefaultRingtonePreferenceTest {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
Context context = spy(ApplicationProvider.getApplicationContext()); 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 */)); mDefaultRingtonePreference = spy(new DefaultRingtonePreference(context, null /* attrs */));
doReturn(context).when(mDefaultRingtonePreference).getContext(); doReturn(context).when(mDefaultRingtonePreference).getContext();
// Use INVALID_RINGTONE_TYPE to return early in RingtoneManager.setActualDefaultRingtoneUri
when(mDefaultRingtonePreference.getRingtoneType()) when(mDefaultRingtonePreference.getRingtoneType())
.thenReturn(RingtoneManager.TYPE_RINGTONE); .thenReturn(INVALID_RINGTONE_TYPE);
mDefaultRingtonePreference.setUserId(1);
mDefaultRingtonePreference.setUserId(OWNER_USER_ID);
mDefaultRingtonePreference.mUserContext = context; 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);
mRingtoneUri = Uri.parse("content://none");
} }
@Test @Test
@@ -79,4 +97,53 @@ public class DefaultRingtonePreferenceTest {
verify(mDefaultRingtonePreference, never()).setActualDefaultRingtoneUri(mRingtoneUri); 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);
}
} }