Loading packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +27 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.media; import android.annotation.Nullable; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; Loading Loading @@ -126,6 +127,8 @@ public class RingtonePlayer implements CoreStartable { Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid=" + Binder.getCallingUid() + ")"); } enforceUriUserId(uri); Client client; synchronized (mClients) { client = mClients.get(token); Loading Loading @@ -207,6 +210,7 @@ public class RingtonePlayer implements CoreStartable { @Override public String getTitle(Uri uri) { enforceUriUserId(uri); final UserHandle user = Binder.getCallingUserHandle(); return Ringtone.getTitle(getContextForUser(user), uri, false /*followSettingsUri*/, false /*allowRemote*/); Loading @@ -214,6 +218,7 @@ public class RingtonePlayer implements CoreStartable { @Override public ParcelFileDescriptor openRingtone(Uri uri) { enforceUriUserId(uri); final UserHandle user = Binder.getCallingUserHandle(); final ContentResolver resolver = getContextForUser(user).getContentResolver(); Loading Loading @@ -241,6 +246,28 @@ public class RingtonePlayer implements CoreStartable { } }; /** * Must be called from the Binder calling thread. * Ensures caller is from the same userId as the content they're trying to access. * @param uri the URI to check * @throws SecurityException when in a non-system call and userId in uri differs from the * caller's userId */ private void enforceUriUserId(Uri uri) throws SecurityException { final int uriUserId = ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()); // for a non-system call, verify the URI to play belongs to the same user as the caller if (UserHandle.isApp(Binder.getCallingUid()) && (UserHandle.myUserId() != uriUserId)) { final String errorMessage = "Illegal access to uri=" + uri + " content associated with user=" + uriUserId + ", current userID: " + UserHandle.myUserId(); if (android.media.audio.Flags.ringtoneUserUriCheck()) { throw new SecurityException(errorMessage); } else { Log.e(TAG, errorMessage, new Exception()); } } } private Context getContextForUser(UserHandle user) { try { return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); Loading packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.ringtone; import static org.junit.Assert.assertThrows; import android.media.AudioAttributes; import android.media.AudioManager; import android.net.Uri; import android.os.Binder; import android.os.UserHandle; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class RingtonePlayerTest extends SysuiTestCase { private AudioManager mAudioManager; private static final String TAG = "RingtonePlayerTest"; @Before public void setup() throws Exception { mAudioManager = getContext().getSystemService(AudioManager.class); } @Test public void testRingtonePlayerUriUserCheck() { android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer(); final AudioAttributes aa = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); // get a UserId that doesn't belong to mine final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0; // build a URI that I shouldn't have access to final Uri uri = new Uri.Builder() .scheme("content").authority(otherUserId + "@media") .appendPath("external").appendPath("downloads") .appendPath("bogusPathThatDoesNotMatter.mp3") .build(); if (android.media.audio.Flags.ringtoneUserUriCheck()) { assertThrows(SecurityException.class, () -> irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/) ); assertThrows(SecurityException.class, () -> irp.getTitle(uri)); } } } services/core/java/com/android/server/audio/AudioService.java +2 −0 Original line number Diff line number Diff line Loading @@ -4997,6 +4997,8 @@ public class AudioService extends IAudioService.Stub pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); pw.println("\tcom.android.media.audio.setStreamVolumeOrder - EOL"); pw.println("\tandroid.media.audio.ringtoneUserUriCheck:" + android.media.audio.Flags.ringtoneUserUriCheck()); pw.println("\tandroid.media.audio.roForegroundAudioControl:" + roForegroundAudioControl()); pw.println("\tandroid.media.audio.scoManagedByAudio:" Loading Loading
packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java +27 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.media; import android.annotation.Nullable; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; Loading Loading @@ -126,6 +127,8 @@ public class RingtonePlayer implements CoreStartable { Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid=" + Binder.getCallingUid() + ")"); } enforceUriUserId(uri); Client client; synchronized (mClients) { client = mClients.get(token); Loading Loading @@ -207,6 +210,7 @@ public class RingtonePlayer implements CoreStartable { @Override public String getTitle(Uri uri) { enforceUriUserId(uri); final UserHandle user = Binder.getCallingUserHandle(); return Ringtone.getTitle(getContextForUser(user), uri, false /*followSettingsUri*/, false /*allowRemote*/); Loading @@ -214,6 +218,7 @@ public class RingtonePlayer implements CoreStartable { @Override public ParcelFileDescriptor openRingtone(Uri uri) { enforceUriUserId(uri); final UserHandle user = Binder.getCallingUserHandle(); final ContentResolver resolver = getContextForUser(user).getContentResolver(); Loading Loading @@ -241,6 +246,28 @@ public class RingtonePlayer implements CoreStartable { } }; /** * Must be called from the Binder calling thread. * Ensures caller is from the same userId as the content they're trying to access. * @param uri the URI to check * @throws SecurityException when in a non-system call and userId in uri differs from the * caller's userId */ private void enforceUriUserId(Uri uri) throws SecurityException { final int uriUserId = ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()); // for a non-system call, verify the URI to play belongs to the same user as the caller if (UserHandle.isApp(Binder.getCallingUid()) && (UserHandle.myUserId() != uriUserId)) { final String errorMessage = "Illegal access to uri=" + uri + " content associated with user=" + uriUserId + ", current userID: " + UserHandle.myUserId(); if (android.media.audio.Flags.ringtoneUserUriCheck()) { throw new SecurityException(errorMessage); } else { Log.e(TAG, errorMessage, new Exception()); } } } private Context getContextForUser(UserHandle user) { try { return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user); Loading
packages/SystemUI/tests/src/com/android/systemui/ringtone/RingtonePlayerTest.java 0 → 100644 +72 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.ringtone; import static org.junit.Assert.assertThrows; import android.media.AudioAttributes; import android.media.AudioManager; import android.net.Uri; import android.os.Binder; import android.os.UserHandle; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class RingtonePlayerTest extends SysuiTestCase { private AudioManager mAudioManager; private static final String TAG = "RingtonePlayerTest"; @Before public void setup() throws Exception { mAudioManager = getContext().getSystemService(AudioManager.class); } @Test public void testRingtonePlayerUriUserCheck() { android.media.IRingtonePlayer irp = mAudioManager.getRingtonePlayer(); final AudioAttributes aa = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build(); // get a UserId that doesn't belong to mine final int otherUserId = UserHandle.myUserId() == 0 ? 10 : 0; // build a URI that I shouldn't have access to final Uri uri = new Uri.Builder() .scheme("content").authority(otherUserId + "@media") .appendPath("external").appendPath("downloads") .appendPath("bogusPathThatDoesNotMatter.mp3") .build(); if (android.media.audio.Flags.ringtoneUserUriCheck()) { assertThrows(SecurityException.class, () -> irp.play(new Binder(), uri, aa, 1.0f /*volume*/, false /*looping*/) ); assertThrows(SecurityException.class, () -> irp.getTitle(uri)); } } }
services/core/java/com/android/server/audio/AudioService.java +2 −0 Original line number Diff line number Diff line Loading @@ -4997,6 +4997,8 @@ public class AudioService extends IAudioService.Stub pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:" + disablePrescaleAbsoluteVolume()); pw.println("\tcom.android.media.audio.setStreamVolumeOrder - EOL"); pw.println("\tandroid.media.audio.ringtoneUserUriCheck:" + android.media.audio.Flags.ringtoneUserUriCheck()); pw.println("\tandroid.media.audio.roForegroundAudioControl:" + roForegroundAudioControl()); pw.println("\tandroid.media.audio.scoManagedByAudio:" Loading