diff --git a/app/build.gradle b/app/build.gradle index be7c5ca1cd..55c10ff25b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -338,7 +338,7 @@ dependencies { implementation 'com.google.protobuf:protobuf-javalite:3.10.0' implementation 'org.signal:argon2:13.1@aar' - implementation 'org.signal:ringrtc-android:2.9.0' + implementation 'org.signal:ringrtc-android:2.9.2' implementation "me.leolin:ShortcutBadger:1.1.16" implementation 'se.emilsjolander:stickylistheaders:2.7.0' diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index 641fbc3604..9660e08213 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -27,9 +27,7 @@ import android.media.AudioManager; import android.os.Build; import android.os.Bundle; import android.util.Rational; -import android.view.View; import android.view.Window; -import android.view.WindowInsetsController; import android.view.WindowManager; import androidx.annotation.NonNull; @@ -37,8 +35,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatDelegate; import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProviders; -import androidx.transition.Transition; -import androidx.transition.TransitionListenerAdapter; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; @@ -47,6 +43,7 @@ import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.components.TooltipPopup; import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow; import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState; +import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil; import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView; @@ -65,7 +62,6 @@ import org.thoughtcrime.securesms.util.EllapsedTimeFormatter; import org.thoughtcrime.securesms.util.FullscreenHelper; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; -import org.thoughtcrime.securesms.util.WindowUtil; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.signalservice.api.messages.calls.HangupMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; @@ -85,6 +81,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE"; private CallParticipantsListUpdatePopupWindow participantUpdateWindow; + private DeviceOrientationMonitor deviceOrientationMonitor; private FullscreenHelper fullscreenHelper; private WebRtcCallView callScreen; @@ -240,7 +237,12 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan } private void initializeViewModel() { - viewModel = ViewModelProviders.of(this).get(WebRtcCallViewModel.class); + deviceOrientationMonitor = new DeviceOrientationMonitor(this); + getLifecycle().addObserver(deviceOrientationMonitor); + + WebRtcCallViewModel.Factory factory = new WebRtcCallViewModel.Factory(deviceOrientationMonitor); + + viewModel = ViewModelProviders.of(this, factory).get(WebRtcCallViewModel.class); viewModel.setIsInPipMode(isInPipMode()); viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled); viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls); @@ -262,6 +264,25 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan } } }); + + viewModel.getOrientation().observe(this, orientation -> { + Intent intent = new Intent(this, WebRtcCallService.class); + intent.setAction(WebRtcCallService.ACTION_ORIENTATION_CHANGED) + .putExtra(WebRtcCallService.EXTRA_ORIENTATION_DEGREES, orientation.getDegrees()); + + startService(intent); + + switch (orientation) { + case LANDSCAPE_LEFT_EDGE: + callScreen.rotateControls(90); + break; + case LANDSCAPE_RIGHT_EDGE: + callScreen.rotateControls(-90); + break; + case PORTRAIT_BOTTOM_EDGE: + callScreen.rotateControls(0); + } + }); } private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/sensors/DeviceOrientationMonitor.java b/app/src/main/java/org/thoughtcrime/securesms/components/sensors/DeviceOrientationMonitor.java new file mode 100644 index 0000000000..501ec576d0 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/sensors/DeviceOrientationMonitor.java @@ -0,0 +1,104 @@ +package org.thoughtcrime.securesms.components.sensors; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Transformations; + +import org.thoughtcrime.securesms.util.ServiceUtil; + +public final class DeviceOrientationMonitor implements DefaultLifecycleObserver { + + private static final float MAGNITUDE_MAXIMUM = 1.5f; + private static final float MAGNITUDE_MINIMUM = 0.75f; + private static final float LANDSCAPE_PITCH_MINIMUM = -0.5f; + private static final float LANDSCAPE_PITCH_MAXIMUM = 0.5f; + + private final SensorManager sensorManager; + private final EventListener eventListener = new EventListener(); + + private final float[] accelerometerReading = new float[3]; + private final float[] magnetometerReading = new float[3]; + + private final float[] rotationMatrix = new float[9]; + private final float[] orientationAngles = new float[3]; + + private final MutableLiveData orientation = new MutableLiveData<>(); + + public DeviceOrientationMonitor(@NonNull Context context) { + this.sensorManager = ServiceUtil.getSensorManager(context); + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + if (accelerometer != null) { + sensorManager.registerListener(eventListener, + accelerometer, + SensorManager.SENSOR_DELAY_NORMAL, + SensorManager.SENSOR_DELAY_UI); + } + Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); + if (magneticField != null) { + sensorManager.registerListener(eventListener, + magneticField, + SensorManager.SENSOR_DELAY_NORMAL, + SensorManager.SENSOR_DELAY_UI); + } + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + sensorManager.unregisterListener(eventListener); + } + + public LiveData getOrientation() { + return Transformations.distinctUntilChanged(orientation); + } + + private void updateOrientationAngles() { + SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading); + SensorManager.getOrientation(rotationMatrix, orientationAngles); + + float pitch = orientationAngles[1]; + float roll = orientationAngles[2]; + float mag = (float) Math.sqrt(Math.pow(pitch, 2) + Math.pow(roll, 2)); + + if (mag > MAGNITUDE_MAXIMUM || mag < MAGNITUDE_MINIMUM) { + return; + } + + if (pitch > LANDSCAPE_PITCH_MINIMUM && pitch < LANDSCAPE_PITCH_MAXIMUM) { + orientation.setValue(roll > 0 ? Orientation.LANDSCAPE_RIGHT_EDGE : Orientation.LANDSCAPE_LEFT_EDGE); + } else { + orientation.setValue(Orientation.PORTRAIT_BOTTOM_EDGE); + } + } + + private final class EventListener implements SensorEventListener { + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.length); + } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.length); + } + + updateOrientationAngles(); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/sensors/Orientation.java b/app/src/main/java/org/thoughtcrime/securesms/components/sensors/Orientation.java new file mode 100644 index 0000000000..5f59b4e9fb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/sensors/Orientation.java @@ -0,0 +1,29 @@ +package org.thoughtcrime.securesms.components.sensors; + +import androidx.annotation.NonNull; + +public enum Orientation { + PORTRAIT_BOTTOM_EDGE(0), + LANDSCAPE_LEFT_EDGE(90), + LANDSCAPE_RIGHT_EDGE(270); + + private final int degrees; + + Orientation(int degrees) { + this.degrees = degrees; + } + + public int getDegrees() { + return degrees; + } + + public static @NonNull Orientation fromDegrees(int degrees) { + for (Orientation orientation : Orientation.values()) { + if (orientation.degrees == degrees) { + return orientation; + } + } + + return PORTRAIT_BOTTOM_EDGE; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/OrientationAwareVideoSink.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/OrientationAwareVideoSink.java new file mode 100644 index 0000000000..2ba8086434 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/OrientationAwareVideoSink.java @@ -0,0 +1,24 @@ +package org.thoughtcrime.securesms.components.webrtc; + +import androidx.annotation.NonNull; + +import org.webrtc.VideoFrame; +import org.webrtc.VideoSink; + +public final class OrientationAwareVideoSink implements VideoSink { + + private final VideoSink delegate; + + public OrientationAwareVideoSink(@NonNull VideoSink delegate) { + this.delegate = delegate; + } + + @Override + public void onFrame(VideoFrame videoFrame) { + if (videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth()) { + delegate.onFrame(new VideoFrame(videoFrame.getBuffer(), 270, videoFrame.getTimestampNs())); + } else { + delegate.onFrame(videoFrame); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java index 96c7b0bf9b..2237967e5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java @@ -120,6 +120,7 @@ public class WebRtcCallView extends FrameLayout { private final Set topViews = new HashSet<>(); private final Set visibleViewSet = new HashSet<>(); private final Set adjustableMarginsSet = new HashSet<>(); + private final Set rotatableControls = new HashSet<>(); private WebRtcControls controls = WebRtcControls.NONE; private final Runnable fadeOutRunnable = () -> { @@ -251,6 +252,16 @@ public class WebRtcCallView extends FrameLayout { controlsListener.onCancelStartCall(); } }); + + rotatableControls.add(hangup); + rotatableControls.add(answer); + rotatableControls.add(answerWithAudio); + rotatableControls.add(audioToggle); + rotatableControls.add(micToggle); + rotatableControls.add(videoToggle); + rotatableControls.add(cameraDirectionToggle); + rotatableControls.add(decline); + rotatableControls.add(smallLocalRender.findViewById(R.id.call_participant_mic_muted)); } @Override @@ -288,6 +299,12 @@ public class WebRtcCallView extends FrameLayout { cancelFadeOut(); } + public void rotateControls(int degrees) { + for (View view : rotatableControls) { + view.animate().rotation(degrees); + } + } + public void setControlsListener(@Nullable ControlsListener controlsListener) { this.controlsListener = controlsListener; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java index f9087da68f..2dac451ba0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java @@ -10,9 +10,12 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.Transformations; import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; import com.annimon.stream.Stream; +import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; +import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.events.CallParticipant; @@ -31,23 +34,25 @@ import org.thoughtcrime.securesms.util.livedata.LiveDataUtil; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; public class WebRtcCallViewModel extends ViewModel { - private final MutableLiveData microphoneEnabled = new MutableLiveData<>(true); - private final MutableLiveData isInPipMode = new MutableLiveData<>(false); - private final MutableLiveData webRtcControls = new MutableLiveData<>(WebRtcControls.NONE); - private final LiveData realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls); - private final SingleLiveEvent events = new SingleLiveEvent(); - private final MutableLiveData elapsed = new MutableLiveData<>(-1L); - private final MutableLiveData liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live()); - private final MutableLiveData participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE); - private final SingleLiveEvent callParticipantListUpdate = new SingleLiveEvent<>(); - private final MutableLiveData> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList()); - private final LiveData safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new); - private final LiveData groupRecipient = LiveDataUtil.filter(Transformations.switchMap(liveRecipient, LiveRecipient::getLiveData), Recipient::isActiveGroup); - private final LiveData> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1); - private final LiveData shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint); + private final MutableLiveData microphoneEnabled = new MutableLiveData<>(true); + private final MutableLiveData isInPipMode = new MutableLiveData<>(false); + private final MutableLiveData webRtcControls = new MutableLiveData<>(WebRtcControls.NONE); + private final LiveData realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls); + private final SingleLiveEvent events = new SingleLiveEvent(); + private final MutableLiveData elapsed = new MutableLiveData<>(-1L); + private final MutableLiveData liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live()); + private final MutableLiveData participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE); + private final SingleLiveEvent callParticipantListUpdate = new SingleLiveEvent<>(); + private final MutableLiveData> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList()); + private final LiveData safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new); + private final LiveData groupRecipient = LiveDataUtil.filter(Transformations.switchMap(liveRecipient, LiveRecipient::getLiveData), Recipient::isActiveGroup); + private final LiveData> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1); + private final LiveData shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint); + private final LiveData orientation; private boolean canDisplayTooltipIfNeeded = true; private boolean hasEnabledLocalVideo = false; @@ -61,6 +66,20 @@ public class WebRtcCallViewModel extends ViewModel { private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication()); + private WebRtcCallViewModel(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) { + orientation = LiveDataUtil.combineLatest(deviceOrientationMonitor.getOrientation(), webRtcControls, (deviceOrientation, controls) -> { + if (controls.canRotateControls()) { + return deviceOrientation; + } else { + return Orientation.PORTRAIT_BOTTOM_EDGE; + } + }); + } + + public LiveData getOrientation() { + return Transformations.distinctUntilChanged(orientation); + } + public LiveData getMicrophoneEnabled() { return Transformations.distinctUntilChanged(microphoneEnabled); } @@ -394,4 +413,18 @@ public class WebRtcCallViewModel extends ViewModel { return recipientIds; } } + + public static class Factory implements ViewModelProvider.Factory { + + private final DeviceOrientationMonitor deviceOrientationMonitor; + + public Factory(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) { + this.deviceOrientationMonitor = deviceOrientationMonitor; + } + + @Override + public @NonNull T create(@NonNull Class modelClass) { + return Objects.requireNonNull(modelClass.cast(new WebRtcCallViewModel(deviceOrientationMonitor))); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java index 79478ac8fd..7ef3732dd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java @@ -51,6 +51,10 @@ public final class WebRtcControls { this.participantLimit = participantLimit; } + boolean canRotateControls() { + return !isGroupCall(); + } + boolean displayErrorControls() { return isError(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java index d3873647c8..da077e6c51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/Camera.java @@ -46,6 +46,7 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa @NonNull private CameraState.Direction activeDirection; private boolean enabled; private boolean isInitialized; + private int orientation; public Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener, @@ -81,6 +82,7 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()), context, observer); + capturer.setOrientation(orientation); isInitialized = true; } } @@ -99,6 +101,15 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa capturer.switchCamera(this); } + @Override + public void setOrientation(@Nullable Integer orientation) { + this.orientation = orientation; + + if (isInitialized && capturer != null) { + capturer.setOrientation(orientation); + } + } + @Override public void setEnabled(boolean enabled) { Log.i(TAG, "setEnabled(): " + enabled); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java index 6cde49d62d..59d00df399 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -34,6 +34,7 @@ import org.signal.zkgroup.VerificationFailedException; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.WebRtcCallActivity; +import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -42,7 +43,6 @@ import org.thoughtcrime.securesms.events.GroupCallPeekEvent; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupManager; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -141,6 +141,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, public static final String EXTRA_GROUP_CALL_UPDATE_GROUP = "group_call_update_group"; public static final String EXTRA_GROUP_CALL_ERA_ID = "era_id"; public static final String EXTRA_RECIPIENT_IDS = "recipient_ids"; + public static final String EXTRA_ORIENTATION_DEGREES = "orientation_degrees"; public static final String ACTION_PRE_JOIN_CALL = "CALL_PRE_JOIN"; public static final String ACTION_CANCEL_PRE_JOIN_CALL = "CANCEL_PRE_JOIN_CALL"; @@ -198,6 +199,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, public static final String ACTION_HTTP_FAILURE = "HTTP_FAILURE"; public static final String ACTION_SEND_OPAQUE_MESSAGE = "SEND_OPAQUE_MESSAGE"; public static final String ACTION_RECEIVE_OPAQUE_MESSAGE = "RECEIVE_OPAQUE_MESSAGE"; + public static final String ACTION_ORIENTATION_CHANGED = "ORIENTATION_CHANGED"; public static final String ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED = "GROUP_LOCAL_DEVICE_CHANGE"; public static final String ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED = "GROUP_REMOTE_DEVICE_CHANGE"; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java index 41519e331d..4484fe9b37 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java @@ -350,6 +350,11 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor { return terminateGroupCall(currentState); } + @Override + protected @NonNull WebRtcServiceState handleOrientationChanged(@NonNull WebRtcServiceState currentState, int orientationDegrees) { + return currentState; + } + public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState) { return terminateGroupCall(currentState, true); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java index f870a1654e..6d7721db7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingCallActionProcessor.java @@ -9,7 +9,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; -import org.signal.ringrtc.CallManager; +import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.events.CallParticipant; @@ -88,8 +88,8 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor { webRtcInteractor.getCallManager().proceed(activePeer.getCallId(), context, videoState.requireEglBase(), - videoState.requireLocalSink(), - callParticipant.getVideoSink(), + new OrientationAwareVideoSink(videoState.requireLocalSink()), + new OrientationAwareVideoSink(callParticipant.getVideoSink()), videoState.requireCamera(), iceServers, hideIp, diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java index ed6b696ee5..351f19592f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/OutgoingCallActionProcessor.java @@ -9,7 +9,7 @@ import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; -import org.signal.ringrtc.CallManager; +import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.events.CallParticipant; @@ -114,8 +114,8 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor { webRtcInteractor.getCallManager().proceed(activePeer.getCallId(), context, videoState.requireEglBase(), - videoState.requireLocalSink(), - callParticipant.getVideoSink(), + new OrientationAwareVideoSink(videoState.requireLocalSink()), + new OrientationAwareVideoSink(callParticipant.getVideoSink()), videoState.requireCamera(), iceServers, isAlwaysTurn, diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java index 0a9496fc15..afab6c177b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcActionProcessor.java @@ -13,12 +13,14 @@ import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; import org.signal.ringrtc.CallManager; import org.signal.ringrtc.GroupCall; +import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.ringrtc.CallState; +import org.thoughtcrime.securesms.ringrtc.Camera; import org.thoughtcrime.securesms.ringrtc.CameraState; import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; @@ -82,6 +84,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_ERROR; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_SUCCESS; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_NETWORK_CHANGE; +import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ORIENTATION_CHANGED; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_OUTGOING_CALL; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_PRE_JOIN_CALL; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVED_OFFER_EXPIRED; @@ -135,6 +138,7 @@ import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIc import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceServers; import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getNullableRemotePeerFromMap; import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOfferMessageType; +import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOrientationDegrees; import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemotePeer; import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemotePeerFromMap; @@ -220,6 +224,7 @@ public abstract class WebRtcActionProcessor { case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, getCameraState(intent)); case ACTION_NETWORK_CHANGE: return handleNetworkChanged(currentState, getAvailable(intent)); case ACTION_BANDWIDTH_MODE_UPDATE: return handleBandwidthModeUpdate(currentState); + case ACTION_ORIENTATION_CHANGED: return handleOrientationChanged(currentState, getOrientationDegrees(intent)); // End Call Actions case ACTION_ENDED_REMOTE_HANGUP: @@ -626,6 +631,18 @@ public abstract class WebRtcActionProcessor { return currentState; } + protected @NonNull WebRtcServiceState handleOrientationChanged(@NonNull WebRtcServiceState currentState, int orientationDegrees) { + Camera camera = currentState.getVideoState().getCamera(); + if (camera != null) { + camera.setOrientation(orientationDegrees); + } + + return currentState.builder() + .changeLocalDeviceState() + .setOrientation(Orientation.fromDegrees(orientationDegrees)) + .build(); + } + //endregion Local device //region End call diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcIntentParser.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcIntentParser.java index 51f4aad438..9da8a2f41b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcIntentParser.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcIntentParser.java @@ -47,6 +47,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_O import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_SDP; import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_TYPE; import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OPAQUE_MESSAGE; +import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ORIENTATION_DEGREES; import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_DEVICE; import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_IDENTITY_KEY; import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_PEER_KEY; @@ -141,6 +142,10 @@ public final class WebRtcIntentParser { return intent.getBooleanExtra(EXTRA_AVAILABLE, false); } + public static int getOrientationDegrees(@NonNull Intent intent) { + return intent.getIntExtra(EXTRA_ORIENTATION_DEGREES, 0); + } + public static @NonNull ArrayList getIceCandidates(@NonNull Intent intent) { return Objects.requireNonNull(intent.getParcelableArrayListExtra(EXTRA_ICE_CANDIDATES)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java index 7a2fbbb5d5..2b113e0bb4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/WebRtcVideoUtil.java @@ -5,6 +5,7 @@ import android.content.Context; import androidx.annotation.NonNull; import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; +import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink; import org.thoughtcrime.securesms.ringrtc.Camera; import org.thoughtcrime.securesms.ringrtc.CameraEventListener; import org.thoughtcrime.securesms.ringrtc.CameraState; @@ -14,6 +15,7 @@ import org.thoughtcrime.securesms.util.Util; import org.webrtc.CapturerObserver; import org.webrtc.EglBase; import org.webrtc.VideoFrame; +import org.webrtc.VideoSink; /** * Helper for initializing, reinitializing, and deinitializing the camera and it's related @@ -34,6 +36,8 @@ public final class WebRtcVideoUtil { BroadcastVideoSink localSink = new BroadcastVideoSink(eglBase); Camera camera = new Camera(context, cameraEventListener, eglBase, CameraState.Direction.FRONT); + camera.setOrientation(currentState.getLocalDeviceState().getOrientation().getDegrees()); + builder.changeVideoState() .eglBase(eglBase) .localSink(localSink) @@ -63,6 +67,8 @@ public final class WebRtcVideoUtil { currentState.getVideoState().requireEglBase(), currentState.getLocalDeviceState().getCameraState().getActiveDirection()); + camera.setOrientation(currentState.getLocalDeviceState().getOrientation().getDegrees()); + builder.changeVideoState() .camera(camera) .commit() @@ -97,8 +103,8 @@ public final class WebRtcVideoUtil { } public static @NonNull WebRtcServiceState initializeVanityCamera(@NonNull WebRtcServiceState currentState) { - Camera camera = currentState.getVideoState().requireCamera(); - BroadcastVideoSink sink = currentState.getVideoState().requireLocalSink(); + Camera camera = currentState.getVideoState().requireCamera(); + VideoSink sink = new OrientationAwareVideoSink(currentState.getVideoState().requireLocalSink()); if (camera.hasCapturer()) { camera.initCapturer(new CapturerObserver() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.java index 541e12e540..fea03b5385 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/LocalDeviceState.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.service.webrtc.state; import androidx.annotation.NonNull; +import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.ringrtc.CameraState; /** @@ -11,19 +12,21 @@ public final class LocalDeviceState { CameraState cameraState; boolean microphoneEnabled; boolean bluetoothAvailable; + Orientation orientation; LocalDeviceState() { - this(CameraState.UNKNOWN, true, false); + this(CameraState.UNKNOWN, true, false, Orientation.PORTRAIT_BOTTOM_EDGE); } LocalDeviceState(@NonNull LocalDeviceState toCopy) { - this(toCopy.cameraState, toCopy.microphoneEnabled, toCopy.bluetoothAvailable); + this(toCopy.cameraState, toCopy.microphoneEnabled, toCopy.bluetoothAvailable, toCopy.orientation); } - LocalDeviceState(@NonNull CameraState cameraState, boolean microphoneEnabled, boolean bluetoothAvailable) { + LocalDeviceState(@NonNull CameraState cameraState, boolean microphoneEnabled, boolean bluetoothAvailable, @NonNull Orientation orientation) { this.cameraState = cameraState; this.microphoneEnabled = microphoneEnabled; this.bluetoothAvailable = bluetoothAvailable; + this.orientation = orientation; } public @NonNull CameraState getCameraState() { @@ -37,4 +40,8 @@ public final class LocalDeviceState { public boolean isBluetoothAvailable() { return bluetoothAvailable; } + + public @NonNull Orientation getOrientation() { + return orientation; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java index af9d47d891..0761bd91d1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java @@ -6,6 +6,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.OptionalLong; import org.signal.ringrtc.GroupCall; +import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.CallParticipantId; @@ -99,6 +100,11 @@ public class WebRtcServiceStateBuilder { toBuild.bluetoothAvailable = available; return this; } + + public @NonNull LocalDeviceStateBuilder setOrientation(@NonNull Orientation orientation) { + toBuild.orientation = orientation; + return this; + } } public class CallSetupStateBuilder { diff --git a/app/witness-verifications.gradle b/app/witness-verifications.gradle index 44d8aa45eb..f494525450 100644 --- a/app/witness-verifications.gradle +++ b/app/witness-verifications.gradle @@ -411,8 +411,8 @@ dependencyVerification { ['org.signal:argon2:13.1', '0f686ccff0d4842bfcc74d92e8dc780a5f159b9376e37a1189fabbcdac458bef'], - ['org.signal:ringrtc-android:2.9.0', - '058ff7dc0c01c1c0db363e7396e357bdcfc6f9a3ddc313c2abae032e979d691f'], + ['org.signal:ringrtc-android:2.9.2', + 'baf77e1f314dce89278af205637d607dbf4607651dee02384f111f43fea29cf6'], ['org.signal:zkgroup-android:0.7.0', '52b172565bd01526e93ebf1796b834bdc449d4fe3422c1b827e49cb8d4f13fbd'],