diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0f55e9c3a6..a74e94c4d5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1113,6 +1113,12 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
+
+
CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id), isVideoCall, false))
+ .subscribeOn(Schedulers.from(singleThreadExecutor))
+ .observeOn(AndroidSchedulers.mainThread())
+ .filter(unused -> requestTime == lastNotificationRequestTime && !stopping)
+ .subscribe(notification -> {
+ lastNotification = notification;
+ startForegroundCompat(lastNotificationId, lastNotification);
+ });
+ }
+ }
+
+ private synchronized void startForegroundCompat(int notificationId, Notification notification) {
+ if (stopping) {
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT >= 30) {
+ startForeground(notificationId, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
+ } else {
+ startForeground(notificationId, notification);
+ }
+ }
+
+ private synchronized void stop() {
+ stopping = true;
+ stopForeground(true);
+ stopSelf();
+ }
+
+ private void registerUncaughtExceptionHandler() {
+ uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager();
+ uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(callManager.getLockManager()));
+ }
+
+ private void registerNetworkReceiver() {
+ if (networkReceiver == null) {
+ networkReceiver = new NetworkReceiver();
+
+ registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+ }
+ }
+
+ private void unregisterNetworkReceiver() {
+ if (networkReceiver != null) {
+ unregisterReceiver(networkReceiver);
+
+ networkReceiver = null;
+ }
+ }
+
+ public void registerPowerButtonReceiver() {
+ if (!AndroidTelecomUtil.getTelecomSupported() && powerButtonReceiver == null) {
+ powerButtonReceiver = new PowerButtonReceiver();
+
+ registerReceiver(powerButtonReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ }
+ }
+
+ public void unregisterPowerButtonReceiver() {
+ if (powerButtonReceiver != null) {
+ unregisterReceiver(powerButtonReceiver);
+
+ powerButtonReceiver = null;
+ }
+ }
+
+ @Override
+ public @Nullable IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onAudioDeviceChanged(@NonNull SignalAudioManager.AudioDevice activeDevice, @NonNull Set availableDevices) {
+ callManager.onAudioDeviceChanged(activeDevice, availableDevices);
+ }
+
+ @Override
+ public void onBluetoothPermissionDenied() {
+ callManager.onBluetoothPermissionDenied();
+ }
+
+ public static PendingIntent getServicePendingIntent(@NonNull Context context, @NonNull Intent intent) {
+ return Build.VERSION.SDK_INT >= 26 ? PendingIntent.getForegroundService(context, 0, intent, PendingIntentFlags.mutable())
+ : PendingIntent.getService(context, 0, intent, PendingIntentFlags.mutable());
+ }
+
+ @SuppressWarnings("deprecation")
+ private class HangUpRtcOnPstnCallAnsweredListener extends PhoneStateListener {
+ @Override
+ public void onCallStateChanged(int state, @NonNull String phoneNumber) {
+ super.onCallStateChanged(state, phoneNumber);
+ if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
+ hangup();
+ Log.i(TAG, "Device phone call ended Signal call.");
+ }
+ }
+
+ private void hangup() {
+ callManager.localHangup();
+ }
+ }
+
+ /**
+ * Periodically request the web socket stay open if we are doing anything call related.
+ */
+ private class WebSocketKeepAliveTask implements Runnable {
+ private boolean keepRunning = false;
+
+ @MainThread
+ public void start() {
+ if (!keepRunning) {
+ keepRunning = true;
+ run();
+ }
+ }
+
+ @MainThread
+ public void stop() {
+ keepRunning = false;
+ ThreadUtil.cancelRunnableOnMain(webSocketKeepAliveTask);
+ ApplicationDependencies.getIncomingMessageObserver().removeKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN);
+ }
+
+ @MainThread
+ @Override
+ public void run() {
+ if (keepRunning) {
+ ApplicationDependencies.getIncomingMessageObserver().registerKeepAliveToken(WEBSOCKET_KEEP_ALIVE_TOKEN);
+ ThreadUtil.runOnMainDelayed(this, REQUEST_WEBSOCKET_STAY_OPEN_DELAY);
+ }
+ }
+ }
+
+ private static class NetworkReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
+
+ ApplicationDependencies.getSignalCallManager().networkChange(activeNetworkInfo != null && activeNetworkInfo.isConnected());
+ ApplicationDependencies.getSignalCallManager().dataModeUpdate();
+ }
+ }
+
+ private static class PowerButtonReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ ApplicationDependencies.getSignalCallManager().screenOff();
+ }
+ }
+ }
+
+ private static class ProximityLockRelease implements Thread.UncaughtExceptionHandler {
+ private final LockManager lockManager;
+
+ private ProximityLockRelease(@NonNull LockManager lockManager) {
+ this.lockManager = lockManager;
+ }
+
+ @Override
+ public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
+ Log.i(TAG, "Uncaught exception - releasing proximity lock", throwable);
+ lockManager.updatePhoneState(LockManager.PhoneState.IDLE);
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java
index 219c3431cd..2e5d5d248b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcInteractor.java
@@ -95,11 +95,11 @@ public class WebRtcInteractor {
}
void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer, boolean isVideoCall) {
- ActiveCallManager.update(context, type, remotePeer.getRecipient().getId(), isVideoCall);
+ WebRtcCallService.update(context, type, remotePeer.getRecipient().getId(), isVideoCall);
}
void setCallInProgressNotification(int type, @NonNull Recipient recipient, boolean isVideoCall) {
- ActiveCallManager.update(context, type, recipient.getId(), isVideoCall);
+ WebRtcCallService.update(context, type, recipient.getId(), isVideoCall);
}
void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
@@ -107,7 +107,7 @@ public class WebRtcInteractor {
}
void stopForegroundService() {
- ActiveCallManager.stop();
+ WebRtcCallService.stop(context);
}
void insertMissedCall(@NonNull RemotePeer remotePeer, long timestamp, boolean isVideoOffer) {
@@ -127,51 +127,51 @@ public class WebRtcInteractor {
}
void registerPowerButtonReceiver() {
- ActiveCallManager.changePowerButtonReceiver(context, true);
+ WebRtcCallService.changePowerButtonReceiver(context, true);
}
void unregisterPowerButtonReceiver() {
- ActiveCallManager.changePowerButtonReceiver(context, false);
+ WebRtcCallService.changePowerButtonReceiver(context, false);
}
void silenceIncomingRinger() {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.SilenceIncomingRinger());
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.SilenceIncomingRinger());
}
void initializeAudioForCall() {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.Initialize());
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.Initialize());
}
void startIncomingRinger(@Nullable Uri ringtoneUri, boolean vibrate) {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.StartIncomingRinger(ringtoneUri, vibrate));
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.StartIncomingRinger(ringtoneUri, vibrate));
}
void startOutgoingRinger() {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.StartOutgoingRinger());
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.StartOutgoingRinger());
}
void stopAudio(boolean playDisconnect) {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.Stop(playDisconnect));
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.Stop(playDisconnect));
}
void startAudioCommunication() {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.Start());
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.Start());
}
public void setUserAudioDevice(@Nullable RecipientId recipientId, @NonNull SignalAudioManager.ChosenAudioDeviceIdentifier userDevice) {
if (userDevice.isLegacy()) {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.SetUserDevice(recipientId, userDevice.getDesiredAudioDeviceLegacy().ordinal(), false));
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.SetUserDevice(recipientId, userDevice.getDesiredAudioDeviceLegacy().ordinal(), false));
} else {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.SetUserDevice(recipientId, userDevice.getDesiredAudioDevice31(), true));
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.SetUserDevice(recipientId, userDevice.getDesiredAudioDevice31(), true));
}
}
public void setDefaultAudioDevice(@NonNull RecipientId recipientId, @NonNull SignalAudioManager.AudioDevice userDevice, boolean clearUserEarpieceSelection) {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.SetDefaultDevice(recipientId, userDevice, clearUserEarpieceSelection));
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.SetDefaultDevice(recipientId, userDevice, clearUserEarpieceSelection));
}
public void playStateChangeUp() {
- ActiveCallManager.sendAudioManagerCommand(context, new AudioManagerCommand.PlayStateChangeUp());
+ WebRtcCallService.sendAudioManagerCommand(context, new AudioManagerCommand.PlayStateChangeUp());
}
void peekGroupCallForRingingCheck(@NonNull GroupCallRingCheckInfo groupCallRingCheckInfo) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
index 82e8e028f0..e99380aa9c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
@@ -116,6 +116,7 @@ public final class FeatureFlags {
private static final String CALLING_REACTIONS = "android.calling.reactions";
private static final String NOTIFICATION_THUMBNAIL_BLOCKLIST = "android.notificationThumbnailProductBlocklist";
private static final String CALLING_RAISE_HAND = "android.calling.raiseHand";
+ private static final String USE_ACTIVE_CALL_MANAGER = "android.calling.useActiveCallManager.5";
private static final String GIF_SEARCH = "global.gifSearch";
private static final String AUDIO_REMUXING = "android.media.audioRemux.1";
private static final String VIDEO_RECORD_1X_ZOOM = "android.media.videoCaptureDefaultZoom";
@@ -197,6 +198,7 @@ public final class FeatureFlags {
CALLING_REACTIONS,
NOTIFICATION_THUMBNAIL_BLOCKLIST,
CALLING_RAISE_HAND,
+ USE_ACTIVE_CALL_MANAGER,
GIF_SEARCH,
AUDIO_REMUXING,
VIDEO_RECORD_1X_ZOOM,
@@ -693,6 +695,11 @@ public final class FeatureFlags {
return getString(NOTIFICATION_THUMBNAIL_BLOCKLIST, "");
}
+ /** Whether or not to use active call manager instead of WebRtcCallService. */
+ public static boolean useActiveCallManager() {
+ return getBoolean(USE_ACTIVE_CALL_MANAGER, false);
+ }
+
/** Whether the in-app GIF search is available for use. */
public static boolean gifSearchAvailable() {
return getBoolean(GIF_SEARCH, true);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java
index a6cc907b87..43df76007f 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallNotificationBuilder.java
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.webrtc.ActiveCallManager;
+import org.thoughtcrime.securesms.service.webrtc.WebRtcCallService;
import org.thoughtcrime.securesms.util.ConversationUtil;
/**
@@ -114,7 +115,7 @@ public class CallNotificationBuilder {
if (deviceVersionSupportsIncomingCallStyle()) {
builder.setStyle(NotificationCompat.CallStyle.forIncomingCall(
person,
- ActiveCallManager.denyCallIntent(context),
+ WebRtcCallService.denyCallIntent(context),
getActivityPendingIntent(context, isVideoCall ? LaunchCallScreenIntentState.VIDEO : LaunchCallScreenIntentState.AUDIO)
).setIsVideo(isVideoCall));
}
@@ -122,7 +123,7 @@ public class CallNotificationBuilder {
return builder.build();
} else if (type == TYPE_OUTGOING_RINGING) {
builder.setContentText(context.getString(R.string.NotificationBarManager__establishing_signal_call));
- builder.addAction(getServiceNotificationAction(context, ActiveCallManager.hangupIntent(context), R.drawable.symbol_phone_down_fill_24, R.string.NotificationBarManager__cancel_call));
+ builder.addAction(getServiceNotificationAction(context, WebRtcCallService.hangupIntent(context), R.drawable.symbol_phone_down_fill_24, R.string.NotificationBarManager__cancel_call));
return builder.build();
} else {
builder.setContentText(getOngoingCallContentText(context, recipient, isVideoCall));
@@ -138,7 +139,7 @@ public class CallNotificationBuilder {
if (deviceVersionSupportsIncomingCallStyle()) {
builder.setStyle(NotificationCompat.CallStyle.forOngoingCall(
person,
- ActiveCallManager.hangupIntent(context)
+ WebRtcCallService.hangupIntent(context)
).setIsVideo(isVideoCall));
}