Validate ringtone URIs before setting am: 7ba175eaeb
am: 236f1a0f17
am: 623d966fac
am: a4a9b9f0e7
am: fa932686c5
am: 179f53c651
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/24956689 Change-Id: Ie1a755c491132beee40f05c27f5f25eb52a414c2 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
setRingtone(uri);
|
if (isValidRingtoneUri(uri)) {
|
||||||
callChangeListener(uri);
|
setRingtone(uri);
|
||||||
|
callChangeListener(uri);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "onActivityResult for URI:" + uri
|
||||||
|
+ " ignored: invalid ringtone Uri");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user