From 42aeceffe2c395d1e8bf10b5db6f500e690a46a1 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Mon, 22 Apr 2024 16:32:27 -0400 Subject: [PATCH] Revert full usage of ActiveCallManager. --- app/src/main/AndroidManifest.xml | 6 + .../service/webrtc/AndroidCallConnection.kt | 8 +- .../service/webrtc/WebRtcCallService.java | 467 ++++++++++++++++++ .../service/webrtc/WebRtcInteractor.java | 30 +- .../securesms/util/FeatureFlags.java | 7 + .../webrtc/CallNotificationBuilder.java | 7 +- 6 files changed, 503 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcCallService.java 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)); }