let Conversations (not Android) play ringtone and vibration

fixes #3972 fixes #3801 fixes #3931
This commit is contained in:
Daniel Gultsch 2021-02-18 20:55:29 +01:00
parent 78c89664c4
commit 484f633180
5 changed files with 77 additions and 41 deletions

View File

@ -12,10 +12,12 @@ import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.Ringtone;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.Vibrator;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
@ -43,6 +45,10 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -70,12 +76,13 @@ import eu.siacs.conversations.xmpp.jingle.Media;
public class NotificationService { public class NotificationService {
private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
public static final Object CATCHUP_LOCK = new Object(); public static final Object CATCHUP_LOCK = new Object();
private static final int LED_COLOR = 0xff00ff00; private static final int LED_COLOR = 0xff00ff00;
private static final int CALL_DAT = 120; private static final long[] CALL_PATTERN = {0, 500, 300, 600};
private static final long[] CALL_PATTERN = {0, 3 * CALL_DAT, CALL_DAT, CALL_DAT, 3 * CALL_DAT, CALL_DAT, CALL_DAT};
private static final String CONVERSATIONS_GROUP = "eu.siacs.conversations"; private static final String CONVERSATIONS_GROUP = "eu.siacs.conversations";
private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024; private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024;
@ -92,6 +99,10 @@ public class NotificationService {
private boolean mIsInForeground; private boolean mIsInForeground;
private long mLastNotification; private long mLastNotification;
private static final String INCOMING_CALLS_NOTIFICATION_CHANNEL = "incoming_calls_channel";
private Ringtone currentlyPlayingRingtone = null;
private ScheduledFuture<?> vibrationFuture;
NotificationService(final XmppConnectionService service) { NotificationService(final XmppConnectionService service) {
this.mXmppConnectionService = service; this.mXmppConnectionService = service;
} }
@ -129,6 +140,7 @@ public class NotificationService {
} }
notificationManager.deleteNotificationChannel("export"); notificationManager.deleteNotificationChannel("export");
notificationManager.deleteNotificationChannel("incoming_calls");
notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("status", c.getString(R.string.notification_group_status_information))); notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("status", c.getString(R.string.notification_group_status_information)));
notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("chats", c.getString(R.string.notification_group_messages))); notificationManager.createNotificationChannelGroup(new NotificationChannelGroup("chats", c.getString(R.string.notification_group_messages)));
@ -162,20 +174,16 @@ public class NotificationService {
exportChannel.setGroup("status"); exportChannel.setGroup("status");
notificationManager.createNotificationChannel(exportChannel); notificationManager.createNotificationChannel(exportChannel);
final NotificationChannel incomingCallsChannel = new NotificationChannel("incoming_calls", final NotificationChannel incomingCallsChannel = new NotificationChannel(INCOMING_CALLS_NOTIFICATION_CHANNEL,
c.getString(R.string.incoming_calls_channel_name), c.getString(R.string.incoming_calls_channel_name),
NotificationManager.IMPORTANCE_HIGH); NotificationManager.IMPORTANCE_HIGH);
incomingCallsChannel.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE), new AudioAttributes.Builder() incomingCallsChannel.setSound(null, null);
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build());
incomingCallsChannel.setShowBadge(false); incomingCallsChannel.setShowBadge(false);
incomingCallsChannel.setLightColor(LED_COLOR); incomingCallsChannel.setLightColor(LED_COLOR);
incomingCallsChannel.enableLights(true); incomingCallsChannel.enableLights(true);
incomingCallsChannel.setGroup("calls"); incomingCallsChannel.setGroup("calls");
incomingCallsChannel.setBypassDnd(true); incomingCallsChannel.setBypassDnd(true);
incomingCallsChannel.enableVibration(true); incomingCallsChannel.enableVibration(false);
incomingCallsChannel.setVibrationPattern(CALL_PATTERN);
notificationManager.createNotificationChannel(incomingCallsChannel); notificationManager.createNotificationChannel(incomingCallsChannel);
final NotificationChannel ongoingCallsChannel = new NotificationChannel("ongoing_calls", final NotificationChannel ongoingCallsChannel = new NotificationChannel("ongoing_calls",
@ -387,14 +395,32 @@ public class NotificationService {
notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification); notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification);
} }
public void showIncomingCallNotification(final AbstractJingleConnection.Id id, final Set<Media> media) { public void startRinging(final AbstractJingleConnection.Id id, final Set<Media> media) {
showIncomingCallNotification(id, media);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
final Resources resources = mXmppConnectionService.getResources();
final Uri uri = Uri.parse(preferences.getString("call_ringtone", resources.getString(R.string.incoming_call_ringtone)));
this.currentlyPlayingRingtone = RingtoneManager.getRingtone(mXmppConnectionService, uri);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
this.currentlyPlayingRingtone.setLooping(true);
}
this.currentlyPlayingRingtone.play();
this.vibrationFuture = SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(
new VibrationRunnable(),
0,
3,
TimeUnit.SECONDS
);
}
private void showIncomingCallNotification(final AbstractJingleConnection.Id id, final Set<Media> media) {
final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class); final Intent fullScreenIntent = new Intent(mXmppConnectionService, RtpSessionActivity.class);
fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.account.getJid().asBareJid().toEscapedString());
fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString()); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_WITH, id.with.toEscapedString());
fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId); fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "incoming_calls"); final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, INCOMING_CALLS_NOTIFICATION_CHANNEL);
if (media.contains(Media.VIDEO)) { if (media.contains(Media.VIDEO)) {
builder.setSmallIcon(R.drawable.ic_videocam_white_24dp); builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
builder.setContentTitle(mXmppConnectionService.getString(R.string.rtp_state_incoming_video_call)); builder.setContentTitle(mXmppConnectionService.getString(R.string.rtp_state_incoming_video_call));
@ -468,9 +494,23 @@ public class NotificationService {
} }
public void cancelIncomingCallNotification() { public void cancelIncomingCallNotification() {
stopSoundAndVibration();
cancel(INCOMING_CALL_NOTIFICATION_ID); cancel(INCOMING_CALL_NOTIFICATION_ID);
} }
public void stopSoundAndVibration() {
if (this.currentlyPlayingRingtone != null) {
if (this.currentlyPlayingRingtone.isPlaying()) {
Log.d(Config.LOGTAG, "stop playing ring tone");
}
this.currentlyPlayingRingtone.stop();
}
if (this.vibrationFuture != null && !this.vibrationFuture.isCancelled()) {
Log.d(Config.LOGTAG, "cancel vibration");
this.vibrationFuture.cancel(true);
}
}
public static void cancelIncomingCallNotification(final Context context) { public static void cancelIncomingCallNotification(final Context context) {
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
try { try {
@ -636,17 +676,7 @@ public class NotificationService {
} }
} }
private void modifyIncomingCall(Builder mBuilder) { private void modifyIncomingCall(final Builder mBuilder) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
final Resources resources = mXmppConnectionService.getResources();
final String ringtone = preferences.getString("call_ringtone", resources.getString(R.string.incoming_call_ringtone));
mBuilder.setVibrate(CALL_PATTERN);
final Uri uri = Uri.parse(ringtone);
try {
mBuilder.setSound(fixRingtoneUri(uri));
} catch (SecurityException e) {
Log.d(Config.LOGTAG, "unable to use custom notification sound " + uri.toString());
}
mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH); mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH);
setNotificationColor(mBuilder); setNotificationColor(mBuilder);
mBuilder.setLights(LED_COLOR, 2000, 3000); mBuilder.setLights(LED_COLOR, 2000, 3000);
@ -1253,4 +1283,13 @@ public class NotificationService {
Log.d(Config.LOGTAG, "unable to cancel notification", e); Log.d(Config.LOGTAG, "unable to cancel notification", e);
} }
} }
private class VibrationRunnable implements Runnable {
@Override
public void run() {
final Vibrator vibrator = (Vibrator) mXmppConnectionService.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(CALL_PATTERN, -1);
}
}
} }

View File

@ -14,6 +14,7 @@ import android.os.PowerManager;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.util.Rational; import android.util.Rational;
import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -146,6 +147,16 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@Override
public boolean onKeyDown(final int keyCode, final KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN){
if (xmppConnectionService != null) {
xmppConnectionService.getNotificationService().stopSoundAndVibration();
}
}
return super.onKeyDown(keyCode, event);
}
private boolean isHelpButtonVisible() { private boolean isHelpButtonVisible() {
try { try {
return STATES_SHOWING_HELP_BUTTON.contains(requireRtpConnection().getEndUserState()); return STATES_SHOWING_HELP_BUTTON.contains(requireRtpConnection().getEndUserState());

View File

@ -16,6 +16,7 @@ import androidx.annotation.BoolRes;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
@ -31,12 +32,10 @@ public class Compatibility {
"led", "led",
"notification_ringtone", "notification_ringtone",
"notification_headsup", "notification_headsup",
"vibrate_on_notification", "vibrate_on_notification"
"call_ringtone"
); );
private static final List<String> UNUESD_SETTINGS_PRE_TWENTYSIX = Arrays.asList( private static final List<String> UNUESD_SETTINGS_PRE_TWENTYSIX = Collections.singletonList(
"message_notification_settings", "message_notification_settings"
"call_notification_settings"
); );

View File

@ -597,7 +597,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void startRinging() { private void startRinging() {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received call from " + id.with + ". start ringing"); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received call from " + id.with + ". start ringing");
ringingTimeoutFuture = jingleConnectionManager.schedule(this::ringingTimeout, BUSY_TIME_OUT, TimeUnit.SECONDS); ringingTimeoutFuture = jingleConnectionManager.schedule(this::ringingTimeout, BUSY_TIME_OUT, TimeUnit.SECONDS);
xmppConnectionService.getNotificationService().showIncomingCallNotification(id, getMedia()); xmppConnectionService.getNotificationService().startRinging(id, getMedia());
} }
private synchronized void ringingTimeout() { private synchronized void ringingTimeout() {

View File

@ -113,19 +113,6 @@
android:value="messages" /> android:value="messages" />
</intent> </intent>
</PreferenceScreen> </PreferenceScreen>
<PreferenceScreen
android:key="call_notification_settings"
android:summary="@string/pref_more_notification_settings_summary"
android:title="@string/pref_incoming_call_notification_settings">
<intent android:action="android.settings.CHANNEL_NOTIFICATION_SETTINGS">
<extra
android:name="android.provider.extra.APP_PACKAGE"
android:value="@string/applicationId" />
<extra
android:name="android.provider.extra.CHANNEL_ID"
android:value="incoming_calls" />
</intent>
</PreferenceScreen>
<RingtonePreference <RingtonePreference
android:defaultValue="@string/notification_ringtone" android:defaultValue="@string/notification_ringtone"
android:key="notification_ringtone" android:key="notification_ringtone"