diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
index 23709a5666..2a63a896b8 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/NotificationsPreferenceFragment.java
@@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
+import org.thoughtcrime.securesms.util.RingtoneUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import static android.app.Activity.RESULT_OK;
@@ -165,10 +166,12 @@ public class NotificationsPreferenceFragment extends ListSummaryPreferenceFragme
if (value == null || TextUtils.isEmpty(value.toString())) {
preference.setSummary(R.string.preferences__silent);
} else {
- Ringtone tone = RingtoneManager.getRingtone(getActivity(), value);
+ Ringtone tone = RingtoneUtil.getRingtone(requireContext(), value);
if (tone != null) {
preference.setSummary(tone.getTitle(getActivity()));
+ } else {
+ preference.setSummary(R.string.preferences__default);
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java
index 32363acf57..206721ca54 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/notifications/CustomNotificationsDialogFragment.java
@@ -24,16 +24,20 @@ import androidx.lifecycle.ViewModelProviders;
import com.annimon.stream.function.Consumer;
+import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.RecipientId;
+import org.thoughtcrime.securesms.util.RingtoneUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Objects;
public class CustomNotificationsDialogFragment extends DialogFragment {
+ private static final String TAG = Log.tag(CustomNotificationsDialogFragment.class);
+
private static final short MESSAGE_RINGTONE_PICKER_REQUEST_CODE = 13562;
private static final short CALL_RINGTONE_PICKER_REQUEST_CODE = 23621;
@@ -224,8 +228,7 @@ public class CustomNotificationsDialogFragment extends DialogFragment {
} else if (ringtone.toString().isEmpty()) {
return context.getString(R.string.preferences__silent);
} else {
- Ringtone tone = RingtoneManager.getRingtone(getActivity(), ringtone);
-
+ Ringtone tone = RingtoneUtil.getRingtone(requireContext(), ringtone);
if (tone != null) {
return tone.getTitle(context);
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RingtoneUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/RingtoneUtil.java
new file mode 100644
index 0000000000..0df1ad0242
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/RingtoneUtil.java
@@ -0,0 +1,121 @@
+package org.thoughtcrime.securesms.util;
+
+import android.annotation.SuppressLint;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.signal.core.util.logging.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Some custom ROMs and some Samsung Android 11 devices have quirks around accessing the default ringtone. This attempts to deal
+ * with them with progressively worse approaches.
+ */
+public final class RingtoneUtil {
+
+ private static final String TAG = Log.tag(RingtoneUtil.class);
+
+ private RingtoneUtil() {}
+
+ public static @Nullable Ringtone getRingtone(@NonNull Context context, @NonNull Uri uri) {
+ Ringtone tone;
+ try {
+ tone = RingtoneManager.getRingtone(context, uri);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Unable to get default ringtone due to permission", e);
+ tone = RingtoneManager.getRingtone(context, RingtoneUtil.getActualDefaultRingtoneUri(context));
+ }
+ return tone;
+ }
+
+ public static @Nullable Uri getActualDefaultRingtoneUri(@NonNull Context context) {
+ Log.i(TAG, "Attempting to get default ringtone directly via normal way");
+ try {
+ return RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE);
+ } catch (SecurityException e) {
+ Log.w(TAG, "Failed to set ringtone with first fallback approach", e);
+ }
+
+ Log.i(TAG, "Attempting to get default ringtone directly via reflection");
+ String uriString = getStringForUser(context.getContentResolver(), getUserId(context));
+ Uri ringtoneUri = uriString != null ? Uri.parse(uriString) : null;
+
+ if (ringtoneUri != null && getUserIdFromAuthority(ringtoneUri.getAuthority(), getUserId(context)) == getUserId(context)) {
+ ringtoneUri = getUriWithoutUserId(ringtoneUri);
+ }
+
+ return ringtoneUri;
+ }
+
+ @SuppressWarnings("JavaReflectionMemberAccess")
+ @SuppressLint("DiscouragedPrivateApi")
+ private static @Nullable String getStringForUser(@NonNull ContentResolver resolver, int userHandle) {
+ try {
+ Method getStringForUser = Settings.System.class.getMethod("getStringForUser", ContentResolver.class, String.class, int.class);
+ return (String) getStringForUser.invoke(Settings.System.class, resolver, Settings.System.RINGTONE, userHandle);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ Log.w(TAG, "Unable to getStringForUser via reflection", e);
+ }
+ return null;
+ }
+
+ @SuppressWarnings("JavaReflectionMemberAccess")
+ @SuppressLint("DiscouragedPrivateApi")
+ private static int getUserId(@NonNull Context context) {
+ try {
+ Object userId = Context.class.getMethod("getUserId").invoke(context);
+ if (userId instanceof Integer) {
+ return (Integer) userId;
+ } else {
+ Log.w(TAG, "getUserId did not return an integer");
+ }
+ } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ Log.w(TAG, "Unable to getUserId via reflection", e);
+ }
+ return 0;
+ }
+
+ private static @Nullable Uri getUriWithoutUserId(@Nullable Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+ Uri.Builder builder = uri.buildUpon();
+ builder.authority(getAuthorityWithoutUserId(uri.getAuthority()));
+ return builder.build();
+ }
+
+ private static @Nullable String getAuthorityWithoutUserId(@Nullable String auth) {
+ if (auth == null) {
+ return null;
+ }
+ int end = auth.lastIndexOf('@');
+ return auth.substring(end + 1);
+ }
+
+ private static int getUserIdFromAuthority(@Nullable String authority, int defaultUserId) {
+ if (authority == null) {
+ return defaultUserId;
+ }
+
+ int end = authority.lastIndexOf('@');
+ if (end == -1) {
+ return defaultUserId;
+ }
+
+ String userIdString = authority.substring(0, end);
+ try {
+ return Integer.parseInt(userIdString);
+ } catch (NumberFormatException e) {
+ return defaultUserId;
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java
index 57cd938f07..e9ba762af3 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/IncomingRinger.java
@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.webrtc.audio;
-import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioManager;
@@ -9,11 +8,13 @@ import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.os.Vibrator;
+import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
+import org.thoughtcrime.securesms.util.RingtoneUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import java.io.IOException;
@@ -24,7 +25,7 @@ public class IncomingRinger {
private static final long[] VIBRATE_PATTERN = {0, 1000, 1000};
- private final Context context;
+ private final Context context;
private final Vibrator vibrator;
private MediaPlayer player;
@@ -37,8 +38,13 @@ public class IncomingRinger {
public void start(@Nullable Uri uri, boolean vibrate) {
AudioManager audioManager = ServiceUtil.getAudioManager(context);
- if (player != null) player.release();
- if (uri != null) player = createPlayer(uri);
+ if (player != null) {
+ player.release();
+ }
+
+ if (uri != null) {
+ player = createPlayer(uri);
+ }
int ringerMode = audioManager.getRingerMode();
@@ -61,7 +67,7 @@ public class IncomingRinger {
player = null;
}
} else {
- Log.w(TAG, "Not ringing, mode: " + ringerMode);
+ Log.w(TAG, "Not ringing, player: " + (player != null ? "available" : "null") + " mode: " + ringerMode);
}
}
@@ -81,11 +87,6 @@ public class IncomingRinger {
return true;
}
- return shouldVibrateNew(context, ringerMode, vibrate);
- }
-
- @TargetApi(Build.VERSION_CODES.HONEYCOMB)
- private boolean shouldVibrateNew(Context context, int ringerMode, boolean vibrate) {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator == null || !vibrator.hasVibrator()) {
@@ -99,26 +100,24 @@ public class IncomingRinger {
}
}
- private boolean shouldVibrateOld(Context context, boolean vibrate) {
- AudioManager audioManager = ServiceUtil.getAudioManager(context);
- return vibrate && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_RINGER);
- }
-
- private MediaPlayer createPlayer(@NonNull Uri ringtoneUri) {
+ private @Nullable MediaPlayer createPlayer(@NonNull Uri ringtoneUri) {
try {
- MediaPlayer mediaPlayer = new MediaPlayer();
+ MediaPlayer mediaPlayer = safeCreatePlayer(ringtoneUri);
+
+ if (mediaPlayer == null) {
+ Log.w(TAG, "Failed to create player for incoming call ringer due to custom rom most likely");
+ return null;
+ }
mediaPlayer.setOnErrorListener(new MediaPlayerErrorListener());
- mediaPlayer.setDataSource(context, ringtoneUri);
mediaPlayer.setLooping(true);
if (Build.VERSION.SDK_INT <= 21) {
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
} else {
- mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
- .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
- .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
- .build());
+ mediaPlayer.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
+ .build());
}
return mediaPlayer;
@@ -128,6 +127,30 @@ public class IncomingRinger {
}
}
+ private @Nullable MediaPlayer safeCreatePlayer(@NonNull Uri ringtoneUri) throws IOException {
+ try {
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(context, ringtoneUri);
+ return mediaPlayer;
+ } catch (SecurityException e) {
+ Log.w(TAG, "Failed to create player with ringtone the normal way", e);
+ }
+
+ if (ringtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
+ try {
+ Uri defaultRingtoneUri = RingtoneUtil.getActualDefaultRingtoneUri(context);
+ if (defaultRingtoneUri != null) {
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(context, defaultRingtoneUri);
+ return mediaPlayer;
+ }
+ } catch (SecurityException e) {
+ Log.w(TAG, "Failed to set default ringtone with fallback approach", e);
+ }
+ }
+
+ return null;
+ }
private class MediaPlayerErrorListener implements MediaPlayer.OnErrorListener {
@Override
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1318a5c504..9020c8e5ca 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2211,6 +2211,7 @@
LED blink pattern
Sound
Silent
+ Default
Repeat alerts
Never
One time