Merge "Resolve cross account user ringtone validation." into main
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 75d8d7d..42d5943 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -3486,7 +3486,8 @@
         return Contacts.getLookupUri(mCallerInfo.getContactId(), mCallerInfo.lookupKey);
     }
 
-    Uri getRingtone() {
+    @VisibleForTesting
+    public Uri getRingtone() {
         return mCallerInfo == null ? null : mCallerInfo.contactRingtoneUri;
     }
 
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
index c740c24..3e71dc2 100644
--- a/src/com/android/server/telecom/RingtoneFactory.java
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -74,12 +74,18 @@
         Ringtone ringtone = null;
 
         if (ringtoneUri != null && userContext != null) {
-            // Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
-            try {
-                ringtone = RingtoneManager.getRingtone(
-                        userContext, ringtoneUri, volumeShaperConfig, audioAttrs);
-            } catch (Exception e) {
-                Log.e(this, e, "getRingtone: exception while getting ringtone.");
+            if (currentUserOwnsRingtone(ringtoneUri, incomingCall)) {
+                // Ringtone URI is explicitly specified and owned by the current user - try to
+                // create a Ringtone with that.
+                try {
+                    ringtone = RingtoneManager.getRingtone(
+                            userContext, ringtoneUri, volumeShaperConfig, audioAttrs);
+                } catch (Exception e) {
+                    Log.e(this, e, "getRingtone: exception while getting ringtone.");
+                }
+            } else {
+                Log.w(this, "getRingtone: Failed to verify that the custom ringtone URI"
+                        + " is owned by the current user. Falling back to the default ringtone.");
             }
         }
         if (ringtone == null) {
@@ -119,6 +125,21 @@
         return new Pair(ringtoneUri, ringtone);
     }
 
+    private static boolean currentUserOwnsRingtone(Uri ringtoneUri, Call incomingCall) {
+        if (TextUtils.isEmpty(ringtoneUri.getUserInfo()) ||
+                incomingCall.getAssociatedUser() == null) {
+            return false;
+        }
+
+        UserHandle associatedUser = incomingCall.getAssociatedUser();
+        if (associatedUser == null) {
+            return false;
+        }
+
+        String currentUserId = String.valueOf(associatedUser.getIdentifier());
+        return currentUserId.equals(ringtoneUri.getUserInfo());
+    }
+
     private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
         return new AudioAttributes.Builder()
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
diff --git a/tests/src/com/android/server/telecom/tests/RingtoneFactoryTest.java b/tests/src/com/android/server/telecom/tests/RingtoneFactoryTest.java
new file mode 100644
index 0000000..2a951aa
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/RingtoneFactoryTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.media.Ringtone;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.RingtoneFactory;
+import com.android.server.telecom.flags.FeatureFlags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class RingtoneFactoryTest extends TelecomTestCase {
+    @Mock private Uri mockCustomRingtoneUri;
+    @Mock private CallsManager mockCallsManager;
+    @Mock private FeatureFlags mockFeatureFlags;
+    @Mock Call mockCall;
+    private RingtoneFactory ringtoneFactory;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = spy(mComponentContextFixture.getTestDouble().getApplicationContext());
+        ringtoneFactory = new RingtoneFactory(mockCallsManager, mContext, mockFeatureFlags);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testCustomRingtoneAccessibleWhenUserOwnsCustomRingtone() throws Exception {
+        // Current User is User 10:
+        when(mockCall.getAssociatedUser()).thenReturn(new UserHandle(10));
+
+        // Custom ringtone is owned by User 10:
+        when(mockCall.getRingtone()).thenReturn(mockCustomRingtoneUri);
+        when(mockCustomRingtoneUri.getUserInfo()).thenReturn("10");
+
+        // Ensure access to the custom ringtone is allowed:
+        Pair<Uri, Ringtone> ringtonePair = ringtoneFactory.getRingtone(mockCall, null, false);
+        assertEquals(mockCustomRingtoneUri, ringtonePair.first);
+    }
+
+    @SmallTest
+    @Test
+    public void testCustomRingtoneNotAccessibleByOtherUser() throws Exception {
+        // Current User is User 10:
+        when(mockCall.getAssociatedUser()).thenReturn(new UserHandle(0));
+
+        // Custom ringtone is owned by User 10:
+        when(mockCall.getRingtone()).thenReturn(mockCustomRingtoneUri);
+        when(mockCustomRingtoneUri.getUserInfo()).thenReturn("10");
+
+        // Ensure access to the custom ringtone is NOT allowed:
+        Pair<Uri, Ringtone> ringtonePair = ringtoneFactory.getRingtone(mockCall, null, false);
+        assertNotEquals(mockCustomRingtoneUri, ringtonePair.first);
+    }
+}
+