diff --git a/app/build.gradle b/app/build.gradle
index c54e65237..43fbff9ad 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -376,6 +376,7 @@ dependencies {
implementation ('androidx.appcompat:appcompat:1.2.0') {
force = true
}
+ implementation "androidx.window:window:1.0.0-alpha09"
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
diff --git a/app/proguard/proguard.cfg b/app/proguard/proguard.cfg
index a7218672f..04d97b2cb 100644
--- a/app/proguard/proguard.cfg
+++ b/app/proguard/proguard.cfg
@@ -9,3 +9,5 @@
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
+
+-keep class androidx.window.** { *; }
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 81126b7e8..e908bc83e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -96,6 +96,7 @@
android:label="@string/app_name"
android:supportsRtl="true"
tools:replace="android:allowBackup"
+ android:resizeableActivity="true"
android:allowBackup="false"
android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true">
@@ -104,6 +105,9 @@
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/>
+
+
@@ -117,16 +121,15 @@
diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java
index dc343acde..d204cfd93 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java
@@ -22,8 +22,10 @@ import android.annotation.SuppressLint;
import android.app.PictureInPictureParams;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
@@ -35,11 +37,16 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
+import androidx.core.util.Consumer;
import androidx.lifecycle.ViewModelProviders;
+import androidx.window.DisplayFeature;
+import androidx.window.FoldingFeature;
+import androidx.window.WindowLayoutInfo;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
+import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
@@ -50,6 +57,7 @@ import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeN
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
+import org.thoughtcrime.securesms.components.webrtc.WebRtcControls;
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
@@ -70,6 +78,7 @@ import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import java.util.List;
+import java.util.Optional;
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
@@ -88,11 +97,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
private DeviceOrientationMonitor deviceOrientationMonitor;
- private FullscreenHelper fullscreenHelper;
- private WebRtcCallView callScreen;
- private TooltipPopup videoTooltip;
- private WebRtcCallViewModel viewModel;
- private boolean enableVideoIfAvailable;
+ private FullscreenHelper fullscreenHelper;
+ private WebRtcCallView callScreen;
+ private TooltipPopup videoTooltip;
+ private WebRtcCallViewModel viewModel;
+ private boolean enableVideoIfAvailable;
+ private androidx.window.WindowManager windowManager;
+ private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
@Override
protected void attachBaseContext(@NonNull Context newBase) {
@@ -100,6 +111,19 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
super.attachBaseContext(newBase);
}
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ if (newConfig.densityDpi >= 480 && getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
+ viewModel.setIsLandscapeEnabled(true);
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ } else if (newConfig.densityDpi < 480){
+ viewModel.setIsLandscapeEnabled(false);
+ }
+ }
+
+ @SuppressLint("SourceLockedOrientationActivity")
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate()");
@@ -107,6 +131,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onCreate(savedInstanceState);
+ boolean isLandscapeEnabled = getResources().getConfiguration().densityDpi >= 480;
+ if (!isLandscapeEnabled) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.webrtc_call_activity);
@@ -115,12 +144,17 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
initializeResources();
- initializeViewModel();
+ initializeViewModel(isLandscapeEnabled);
processIntent(getIntent());
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
+
+ windowManager = new androidx.window.WindowManager(this);
+ windowLayoutInfoConsumer = new WindowLayoutInfoConsumer(windowManager);
+
+ windowManager.registerLayoutChangeCallback(SignalExecutors.BOUNDED, windowLayoutInfoConsumer);
}
@Override
@@ -178,6 +212,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
@Override
protected void onDestroy() {
super.onDestroy();
+ windowManager.unregisterLayoutChangeCallback(windowLayoutInfoConsumer);
EventBus.getDefault().unregister(this);
}
@@ -208,8 +243,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private boolean enterPipModeIfPossible() {
if (viewModel.canEnterPipMode() && isSystemPipEnabledAndAvailable()) {
PictureInPictureParams params = new PictureInPictureParams.Builder()
- .setAspectRatio(new Rational(9, 16))
- .build();
+ .setAspectRatio(new Rational(9, 16))
+ .build();
enterPictureInPictureMode(params);
CallParticipantsListDialog.dismiss(getSupportFragmentManager());
@@ -248,13 +283,14 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen);
}
- private void initializeViewModel() {
+ private void initializeViewModel(boolean isLandscapeEnabled) {
deviceOrientationMonitor = new DeviceOrientationMonitor(this);
getLifecycle().addObserver(deviceOrientationMonitor);
WebRtcCallViewModel.Factory factory = new WebRtcCallViewModel.Factory(deviceOrientationMonitor);
viewModel = ViewModelProviders.of(this, factory).get(WebRtcCallViewModel.class);
+ viewModel.setIsLandscapeEnabled(isLandscapeEnabled);
viewModel.setIsInPipMode(isInPipMode());
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
@@ -276,25 +312,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
}
});
- viewModel.getOrientation().observe(this, orientation -> {
- ApplicationDependencies.getSignalCallManager().orientationChanged(orientation.getDegrees());
-
- 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);
- }
- });
+ viewModel.getOrientationAndLandscapeEnabled().observe(this, pair -> ApplicationDependencies.getSignalCallManager().orientationChanged(pair.second, pair.first.getDegrees()));
+ viewModel.getControlsRotation().observe(this, callScreen::rotateControls);
}
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
if (event instanceof WebRtcCallViewModel.Event.StartCall) {
- startCall(((WebRtcCallViewModel.Event.StartCall)event).isVideoCall());
+ startCall(((WebRtcCallViewModel.Event.StartCall) event).isVideoCall());
return;
} else if (event instanceof WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) {
SafetyNumberChangeDialog.showForGroupCall(getSupportFragmentManager(), ((WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) event).getIdentityRecords());
@@ -406,7 +430,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, recipient.getDisplayName(this)),
- R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
+ R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> {
callScreen.setRecipient(recipient);
@@ -490,13 +514,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
new AlertDialog.Builder(this)
- .setTitle(R.string.RedPhone_number_not_registered)
- .setIcon(R.drawable.ic_warning)
- .setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
- .setCancelable(true)
- .setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
- .setOnCancelListener(d -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
- .show();
+ .setTitle(R.string.RedPhone_number_not_registered)
+ .setIcon(R.drawable.ic_warning)
+ .setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
+ .setCancelable(true)
+ .setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
+ .setOnCancelListener(d -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
+ .show();
}
private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) {
@@ -588,20 +612,34 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
callScreen.setRecipient(event.getRecipient());
switch (event.getState()) {
- case CALL_PRE_JOIN: handleCallPreJoin(event); break;
- case CALL_CONNECTED: handleCallConnected(event); break;
- case NETWORK_FAILURE: handleServerFailure(); break;
- case CALL_RINGING: handleCallRinging(); break;
- case CALL_DISCONNECTED: handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
- case CALL_ACCEPTED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.ACCEPTED); break;
- case CALL_DECLINED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.DECLINED); break;
- case CALL_ONGOING_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.BUSY); break;
- case CALL_NEEDS_PERMISSION: handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break;
- case NO_SUCH_USER: handleNoSuchUser(event); break;
- case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(); break;
- case CALL_OUTGOING: handleOutgoingCall(event); break;
- case CALL_BUSY: handleCallBusy(); break;
- case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
+ case CALL_PRE_JOIN:
+ handleCallPreJoin(event); break;
+ case CALL_CONNECTED:
+ handleCallConnected(event); break;
+ case NETWORK_FAILURE:
+ handleServerFailure(); break;
+ case CALL_RINGING:
+ handleCallRinging(); break;
+ case CALL_DISCONNECTED:
+ handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
+ case CALL_ACCEPTED_ELSEWHERE:
+ handleTerminate(event.getRecipient(), HangupMessage.Type.ACCEPTED); break;
+ case CALL_DECLINED_ELSEWHERE:
+ handleTerminate(event.getRecipient(), HangupMessage.Type.DECLINED); break;
+ case CALL_ONGOING_ELSEWHERE:
+ handleTerminate(event.getRecipient(), HangupMessage.Type.BUSY); break;
+ case CALL_NEEDS_PERMISSION:
+ handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break;
+ case NO_SUCH_USER:
+ handleNoSuchUser(event); break;
+ case RECIPIENT_UNAVAILABLE:
+ handleRecipientUnavailable(); break;
+ case CALL_OUTGOING:
+ handleOutgoingCall(event); break;
+ case CALL_BUSY:
+ handleCallBusy(); break;
+ case UNTRUSTED_IDENTITY:
+ handleUntrustedIdentity(event); break;
}
boolean enableVideo = event.getLocalParticipant().getCameraState().getCameraCount() > 0 && enableVideoIfAvailable;
@@ -732,4 +770,31 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
viewModel.onLocalPictureInPictureClicked();
}
}
+
+ private class WindowLayoutInfoConsumer implements Consumer {
+
+ private final androidx.window.WindowManager windowManager;
+
+ private WindowLayoutInfoConsumer(androidx.window.WindowManager windowManager) {
+ this.windowManager = windowManager;
+ }
+
+ @Override
+ public void accept(WindowLayoutInfo windowLayoutInfo) {
+ Log.d(TAG, "On WindowLayoutInfo accepted: " + windowLayoutInfo.toString());
+
+ Optional feature = windowLayoutInfo.getDisplayFeatures().stream().filter(f -> f instanceof FoldingFeature).findFirst();
+ if (feature.isPresent()) {
+ FoldingFeature foldingFeature = (FoldingFeature) feature.get();
+ Rect bounds = foldingFeature.getBounds();
+ if (foldingFeature.getState() == FoldingFeature.State.HALF_OPENED && bounds.top == bounds.bottom) {
+ Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in table-top display mode");
+ viewModel.setFoldableState(WebRtcControls.FoldableState.folded(bounds.top));
+ } else {
+ Log.d(TAG, "OnWindowLayoutInfo accepted: ensure call view is in flat display mode");
+ viewModel.setFoldableState(WebRtcControls.FoldableState.flat());
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/BroadcastVideoSink.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/BroadcastVideoSink.java
index e68351d62..b7c45771b 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/BroadcastVideoSink.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/BroadcastVideoSink.java
@@ -20,6 +20,8 @@ import java.util.WeakHashMap;
*/
public class BroadcastVideoSink implements VideoSink {
+ public static final int DEVICE_ROTATION_IGNORE = -1;
+
private final EglBase eglBase;
private final WeakHashMap sinks;
private final WeakHashMap