Merge cherrypicks of ['googleplex-android-review.googlesource.com/25007539', 'googleplex-android-review.googlesource.com/24956689'] into security-aosp-tm-release.
Change-Id: I686ce700b5141944db1c17c0a7bcbb8d9b05027f
diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java
index 9bf626c..4c65488 100644
--- a/src/com/android/settings/DefaultRingtonePreference.java
+++ b/src/com/android/settings/DefaultRingtonePreference.java
@@ -51,16 +51,9 @@
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;
}
diff --git a/src/com/android/settings/RingtonePreference.java b/src/com/android/settings/RingtonePreference.java
index 8f9c618..d283e39 100644
--- a/src/com/android/settings/RingtonePreference.java
+++ b/src/com/android/settings/RingtonePreference.java
@@ -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.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,82 @@
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;
+ }
+
}
diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
index a6b565a..9ea8c58 100644
--- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
+++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java
@@ -17,6 +17,7 @@
package com.android.settings.notification;
+import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static com.android.internal.notification.NotificationAccessConfirmationActivityContract.EXTRA_COMPONENT_NAME;
@@ -26,6 +27,7 @@
import android.annotation.Nullable;
import android.app.Activity;
import android.app.NotificationManager;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -35,10 +37,12 @@
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Slog;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.Toast;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
@@ -55,12 +59,28 @@
private ComponentName mComponentName;
private NotificationManager mNm;
+ private DevicePolicyManager mDpm;
+ private UserManager mUm;
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ mUm = getSystemService(UserManager.class);
+ mDpm = getSystemService(DevicePolicyManager.class);
+
+ if (mUm.isManagedProfile()) {
+ Slog.w(LOG_TAG, "Apps in the work profile do not support notification listeners");
+ Toast.makeText(this,
+ mDpm.getResources().getString(WORK_APPS_CANNOT_ACCESS_NOTIFICATION_SETTINGS,
+ () -> getString(R.string.notification_settings_work_profile)),
+ Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mComponentName = getIntent().getParcelableExtra(EXTRA_COMPONENT_NAME);
diff --git a/src/com/android/settings/notification/app/NotificationSoundPreference.java b/src/com/android/settings/notification/app/NotificationSoundPreference.java
index 136b21f..b55f9bd 100644
--- a/src/com/android/settings/notification/app/NotificationSoundPreference.java
+++ b/src/com/android/settings/notification/app/NotificationSoundPreference.java
@@ -25,10 +25,13 @@
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 boolean onActivityResult(int requestCode, int resultCode, Intent data) {
if (data != null) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
- setRingtone(uri);
- callChangeListener(uri);
+ if (isValidRingtoneUri(uri)) {
+ setRingtone(uri);
+ callChangeListener(uri);
+ } else {
+ Log.e(TAG, "onActivityResult for URI:" + uri
+ + " ignored: invalid ringtone Uri");
+ }
}
return true;
diff --git a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java
index 7877684..360a8a5 100644
--- a/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java
+++ b/tests/unit/src/com/android/settings/DefaultRingtonePreferenceTest.java
@@ -16,16 +16,19 @@
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.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 +37,22 @@
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 +60,24 @@
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);
+
+ mRingtoneUri = Uri.parse("content://none");
}
@Test
@@ -79,4 +97,53 @@
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);
+ }
}