Implement initial support for foldables in calling.

fork-5.53.8
Alex Hart 2021-07-23 13:48:09 -03:00 zatwierdzone przez Greyson Parrelli
rodzic 927b6096c6
commit 5229e24397
20 zmienionych plików z 590 dodań i 126 usunięć

Wyświetl plik

@ -376,6 +376,7 @@ dependencies {
implementation ('androidx.appcompat:appcompat:1.2.0') { implementation ('androidx.appcompat:appcompat:1.2.0') {
force = true force = true
} }
implementation "androidx.window:window:1.0.0-alpha09"
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0' implementation 'androidx.legacy:legacy-support-v13:1.0.0'

Wyświetl plik

@ -9,3 +9,5 @@
# Protobuf lite # Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; } -keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
-keep class androidx.window.** { *; }

Wyświetl plik

@ -96,6 +96,7 @@
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
tools:replace="android:allowBackup" tools:replace="android:allowBackup"
android:resizeableActivity="true"
android:allowBackup="false" android:allowBackup="false"
android:theme="@style/TextSecure.LightTheme" android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true"> android:largeHeap="true">
@ -104,6 +105,9 @@
android:name="com.google.android.geo.API_KEY" android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/> android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/>
<meta-data android:name="android.supports_size_changes"
android:value="true" />
<meta-data android:name="com.google.android.gms.version" <meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" /> android:value="@integer/google_play_services_version" />
@ -117,16 +121,15 @@
<activity android:name=".WebRtcCallActivity" <activity android:name=".WebRtcCallActivity"
android:theme="@style/TextSecure.DarkTheme.WebRTCCall" android:theme="@style/TextSecure.DarkTheme.WebRTCCall"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:supportsPictureInPicture="true" android:supportsPictureInPicture="true"
android:windowSoftInputMode="stateAlwaysHidden" android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity=".calling" android:taskAffinity=".calling"
android:resizeableActivity="true"
android:launchMode="singleTask"/> android:launchMode="singleTask"/>
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity" <activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
android:theme="@style/TextSecure.DarkNoActionBar" android:theme="@style/TextSecure.DarkNoActionBar"
android:screenOrientation="portrait"
android:noHistory="true" android:noHistory="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>

Wyświetl plik

@ -22,8 +22,10 @@ import android.annotation.SuppressLint;
import android.app.PictureInPictureParams; import android.app.PictureInPictureParams;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Rect;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@ -35,11 +37,16 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.lifecycle.ViewModelProviders; 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.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.TooltipPopup; import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; 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.WebRtcAudioOutput;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView; import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel; 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.components.webrtc.participantslist.CallParticipantsListDialog;
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog; import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; 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 org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import java.util.List; import java.util.List;
import java.util.Optional;
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE; 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 CallParticipantsListUpdatePopupWindow participantUpdateWindow;
private DeviceOrientationMonitor deviceOrientationMonitor; private DeviceOrientationMonitor deviceOrientationMonitor;
private FullscreenHelper fullscreenHelper; private FullscreenHelper fullscreenHelper;
private WebRtcCallView callScreen; private WebRtcCallView callScreen;
private TooltipPopup videoTooltip; private TooltipPopup videoTooltip;
private WebRtcCallViewModel viewModel; private WebRtcCallViewModel viewModel;
private boolean enableVideoIfAvailable; private boolean enableVideoIfAvailable;
private androidx.window.WindowManager windowManager;
private WindowLayoutInfoConsumer windowLayoutInfoConsumer;
@Override @Override
protected void attachBaseContext(@NonNull Context newBase) { protected void attachBaseContext(@NonNull Context newBase) {
@ -100,6 +111,19 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
super.attachBaseContext(newBase); 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate()"); Log.i(TAG, "onCreate()");
@ -107,6 +131,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
boolean isLandscapeEnabled = getResources().getConfiguration().densityDpi >= 480;
if (!isLandscapeEnabled) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.webrtc_call_activity); setContentView(R.layout.webrtc_call_activity);
@ -115,12 +144,17 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
initializeResources(); initializeResources();
initializeViewModel(); initializeViewModel(isLandscapeEnabled);
processIntent(getIntent()); processIntent(getIntent());
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false); enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE); getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
windowManager = new androidx.window.WindowManager(this);
windowLayoutInfoConsumer = new WindowLayoutInfoConsumer(windowManager);
windowManager.registerLayoutChangeCallback(SignalExecutors.BOUNDED, windowLayoutInfoConsumer);
} }
@Override @Override
@ -178,6 +212,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
windowManager.unregisterLayoutChangeCallback(windowLayoutInfoConsumer);
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
} }
@ -208,8 +243,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private boolean enterPipModeIfPossible() { private boolean enterPipModeIfPossible() {
if (viewModel.canEnterPipMode() && isSystemPipEnabledAndAvailable()) { if (viewModel.canEnterPipMode() && isSystemPipEnabledAndAvailable()) {
PictureInPictureParams params = new PictureInPictureParams.Builder() PictureInPictureParams params = new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(9, 16)) .setAspectRatio(new Rational(9, 16))
.build(); .build();
enterPictureInPictureMode(params); enterPictureInPictureMode(params);
CallParticipantsListDialog.dismiss(getSupportFragmentManager()); CallParticipantsListDialog.dismiss(getSupportFragmentManager());
@ -248,13 +283,14 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen); participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen);
} }
private void initializeViewModel() { private void initializeViewModel(boolean isLandscapeEnabled) {
deviceOrientationMonitor = new DeviceOrientationMonitor(this); deviceOrientationMonitor = new DeviceOrientationMonitor(this);
getLifecycle().addObserver(deviceOrientationMonitor); getLifecycle().addObserver(deviceOrientationMonitor);
WebRtcCallViewModel.Factory factory = new WebRtcCallViewModel.Factory(deviceOrientationMonitor); WebRtcCallViewModel.Factory factory = new WebRtcCallViewModel.Factory(deviceOrientationMonitor);
viewModel = ViewModelProviders.of(this, factory).get(WebRtcCallViewModel.class); viewModel = ViewModelProviders.of(this, factory).get(WebRtcCallViewModel.class);
viewModel.setIsLandscapeEnabled(isLandscapeEnabled);
viewModel.setIsInPipMode(isInPipMode()); viewModel.setIsInPipMode(isInPipMode());
viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled); viewModel.getMicrophoneEnabled().observe(this, callScreen::setMicEnabled);
viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls); viewModel.getWebRtcControls().observe(this, callScreen::setWebRtcControls);
@ -276,25 +312,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
} }
}); });
viewModel.getOrientation().observe(this, orientation -> { viewModel.getOrientationAndLandscapeEnabled().observe(this, pair -> ApplicationDependencies.getSignalCallManager().orientationChanged(pair.second, pair.first.getDegrees()));
ApplicationDependencies.getSignalCallManager().orientationChanged(orientation.getDegrees()); viewModel.getControlsRotation().observe(this, callScreen::rotateControls);
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) { private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
if (event instanceof WebRtcCallViewModel.Event.StartCall) { if (event instanceof WebRtcCallViewModel.Event.StartCall) {
startCall(((WebRtcCallViewModel.Event.StartCall)event).isVideoCall()); startCall(((WebRtcCallViewModel.Event.StartCall) event).isVideoCall());
return; return;
} else if (event instanceof WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) { } else if (event instanceof WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) {
SafetyNumberChangeDialog.showForGroupCall(getSupportFragmentManager(), ((WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) event).getIdentityRecords()); 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) .request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary() .ifNecessary()
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, recipient.getDisplayName(this)), .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)) .withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> { .onAllGranted(() -> {
callScreen.setRecipient(recipient); callScreen.setRecipient(recipient);
@ -490,13 +514,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) { 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 if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
new AlertDialog.Builder(this) new AlertDialog.Builder(this)
.setTitle(R.string.RedPhone_number_not_registered) .setTitle(R.string.RedPhone_number_not_registered)
.setIcon(R.drawable.ic_warning) .setIcon(R.drawable.ic_warning)
.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice) .setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
.setCancelable(true) .setCancelable(true)
.setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL)) .setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
.setOnCancelListener(d -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL)) .setOnCancelListener(d -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
.show(); .show();
} }
private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) { private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) {
@ -588,20 +612,34 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
callScreen.setRecipient(event.getRecipient()); callScreen.setRecipient(event.getRecipient());
switch (event.getState()) { switch (event.getState()) {
case CALL_PRE_JOIN: handleCallPreJoin(event); break; case CALL_PRE_JOIN:
case CALL_CONNECTED: handleCallConnected(event); break; handleCallPreJoin(event); break;
case NETWORK_FAILURE: handleServerFailure(); break; case CALL_CONNECTED:
case CALL_RINGING: handleCallRinging(); break; handleCallConnected(event); break;
case CALL_DISCONNECTED: handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break; case NETWORK_FAILURE:
case CALL_ACCEPTED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.ACCEPTED); break; handleServerFailure(); break;
case CALL_DECLINED_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.DECLINED); break; case CALL_RINGING:
case CALL_ONGOING_ELSEWHERE: handleTerminate(event.getRecipient(), HangupMessage.Type.BUSY); break; handleCallRinging(); break;
case CALL_NEEDS_PERMISSION: handleTerminate(event.getRecipient(), HangupMessage.Type.NEED_PERMISSION); break; case CALL_DISCONNECTED:
case NO_SUCH_USER: handleNoSuchUser(event); break; handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL); break;
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(); break; case CALL_ACCEPTED_ELSEWHERE:
case CALL_OUTGOING: handleOutgoingCall(event); break; handleTerminate(event.getRecipient(), HangupMessage.Type.ACCEPTED); break;
case CALL_BUSY: handleCallBusy(); break; case CALL_DECLINED_ELSEWHERE:
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break; 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; boolean enableVideo = event.getLocalParticipant().getCameraState().getCameraCount() > 0 && enableVideoIfAvailable;
@ -732,4 +770,31 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
viewModel.onLocalPictureInPictureClicked(); viewModel.onLocalPictureInPictureClicked();
} }
} }
private class WindowLayoutInfoConsumer implements Consumer<WindowLayoutInfo> {
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<DisplayFeature> 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());
}
}
}
}
} }

Wyświetl plik

@ -20,6 +20,8 @@ import java.util.WeakHashMap;
*/ */
public class BroadcastVideoSink implements VideoSink { public class BroadcastVideoSink implements VideoSink {
public static final int DEVICE_ROTATION_IGNORE = -1;
private final EglBase eglBase; private final EglBase eglBase;
private final WeakHashMap<VideoSink, Boolean> sinks; private final WeakHashMap<VideoSink, Boolean> sinks;
private final WeakHashMap<Object, Point> requestingSizes; private final WeakHashMap<Object, Point> requestingSizes;
@ -85,7 +87,10 @@ public class BroadcastVideoSink implements VideoSink {
@Override @Override
public synchronized void onFrame(@NonNull VideoFrame videoFrame) { public synchronized void onFrame(@NonNull VideoFrame videoFrame) {
if (videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth() || forceRotate) { boolean isDeviceRotationIgnored = deviceOrientationDegrees == DEVICE_ROTATION_IGNORE;
boolean isWideVideoFrame = videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth();
if (!isDeviceRotationIgnored && (isWideVideoFrame || forceRotate)) {
int rotation = calculateRotation(); int rotation = calculateRotation();
if (rotation > 0) { if (rotation > 0) {
rotation += rotateWithDevice ? videoFrame.getRotation() : 0; rotation += rotateWithDevice ? videoFrame.getRotation() : 0;

Wyświetl plik

@ -34,6 +34,7 @@ public class CallParticipantsLayout extends FlexboxLayout {
private CallParticipant focusedParticipant = null; private CallParticipant focusedParticipant = null;
private boolean shouldRenderInPip; private boolean shouldRenderInPip;
private boolean isPortrait; private boolean isPortrait;
private LayoutStrategy layoutStrategy;
public CallParticipantsLayout(@NonNull Context context) { public CallParticipantsLayout(@NonNull Context context) {
super(context); super(context);
@ -47,11 +48,18 @@ public class CallParticipantsLayout extends FlexboxLayout {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
} }
void update(@NonNull List<CallParticipant> callParticipants, @NonNull CallParticipant focusedParticipant, boolean shouldRenderInPip, boolean isPortrait) { void update(@NonNull List<CallParticipant> callParticipants,
@NonNull CallParticipant focusedParticipant,
boolean shouldRenderInPip,
boolean isPortrait,
@NonNull LayoutStrategy layoutStrategy)
{
this.callParticipants = callParticipants; this.callParticipants = callParticipants;
this.focusedParticipant = focusedParticipant; this.focusedParticipant = focusedParticipant;
this.shouldRenderInPip = shouldRenderInPip; this.shouldRenderInPip = shouldRenderInPip;
this.isPortrait = isPortrait; this.isPortrait = isPortrait;
this.layoutStrategy = layoutStrategy;
setFlexDirection(layoutStrategy.getFlexDirection());
updateLayout(); updateLayout();
} }
@ -127,7 +135,7 @@ public class CallParticipantsLayout extends FlexboxLayout {
callParticipantView.useLargeAvatar(); callParticipantView.useLargeAvatar();
} }
setChildLayoutParams(view, index, getChildCount()); layoutStrategy.setChildLayoutParams(view, index, getChildCount());
} }
private void addCallParticipantView() { private void addCallParticipantView() {
@ -139,17 +147,9 @@ public class CallParticipantsLayout extends FlexboxLayout {
addView(view); addView(view);
} }
private void setChildLayoutParams(@NonNull View child, int childPosition, int childCount) { public interface LayoutStrategy {
FlexboxLayout.LayoutParams params = (FlexboxLayout.LayoutParams) child.getLayoutParams(); int getFlexDirection();
if (childCount < 3) {
params.setFlexBasisPercent(1f); void setChildLayoutParams(@NonNull View child, int childPosition, int childCount);
} else {
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
params.setFlexBasisPercent(1f);
} else {
params.setFlexBasisPercent(0.5f);
}
}
child.setLayoutParams(params);
} }
} }

Wyświetl plik

@ -0,0 +1,53 @@
package org.thoughtcrime.securesms.components.webrtc
import android.view.View
import com.google.android.flexbox.FlexDirection
import com.google.android.flexbox.FlexboxLayout
object CallParticipantsLayoutStrategies {
private object Portrait : CallParticipantsLayout.LayoutStrategy {
override fun getFlexDirection(): Int = FlexDirection.ROW
override fun setChildLayoutParams(child: View, childPosition: Int, childCount: Int) {
val params = child.layoutParams as FlexboxLayout.LayoutParams
if (childCount < 3) {
params.flexBasisPercent = 1f
} else {
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
params.flexBasisPercent = 1f
} else {
params.flexBasisPercent = 0.5f
}
}
child.layoutParams = params
}
}
private object Landscape : CallParticipantsLayout.LayoutStrategy {
override fun getFlexDirection() = FlexDirection.COLUMN
override fun setChildLayoutParams(child: View, childPosition: Int, childCount: Int) {
val params = child.layoutParams as FlexboxLayout.LayoutParams
if (childCount < 4) {
params.flexBasisPercent = 1f
} else {
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
params.flexBasisPercent = 1f
} else {
params.flexBasisPercent = 0.5f
}
}
child.layoutParams = params
}
}
@JvmStatic
fun getStrategy(isPortrait: Boolean): CallParticipantsLayout.LayoutStrategy {
return if (isPortrait) {
Portrait
} else {
Landscape
}
}
}

Wyświetl plik

@ -36,7 +36,8 @@ public final class CallParticipantsState {
false, false,
false, false,
false, false,
OptionalLong.empty()); OptionalLong.empty(),
WebRtcControls.FoldableState.flat());
private final WebRtcViewModel.State callState; private final WebRtcViewModel.State callState;
private final WebRtcViewModel.GroupCallState groupCallState; private final WebRtcViewModel.GroupCallState groupCallState;
@ -48,6 +49,7 @@ public final class CallParticipantsState {
private final boolean showVideoForOutgoing; private final boolean showVideoForOutgoing;
private final boolean isViewingFocusedParticipant; private final boolean isViewingFocusedParticipant;
private final OptionalLong remoteDevicesCount; private final OptionalLong remoteDevicesCount;
private final WebRtcControls.FoldableState foldableState;
public CallParticipantsState(@NonNull WebRtcViewModel.State callState, public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
@NonNull WebRtcViewModel.GroupCallState groupCallState, @NonNull WebRtcViewModel.GroupCallState groupCallState,
@ -58,7 +60,8 @@ public final class CallParticipantsState {
boolean isInPipMode, boolean isInPipMode,
boolean showVideoForOutgoing, boolean showVideoForOutgoing,
boolean isViewingFocusedParticipant, boolean isViewingFocusedParticipant,
OptionalLong remoteDevicesCount) OptionalLong remoteDevicesCount,
@NonNull WebRtcControls.FoldableState foldableState)
{ {
this.callState = callState; this.callState = callState;
this.groupCallState = groupCallState; this.groupCallState = groupCallState;
@ -70,6 +73,7 @@ public final class CallParticipantsState {
this.showVideoForOutgoing = showVideoForOutgoing; this.showVideoForOutgoing = showVideoForOutgoing;
this.isViewingFocusedParticipant = isViewingFocusedParticipant; this.isViewingFocusedParticipant = isViewingFocusedParticipant;
this.remoteDevicesCount = remoteDevicesCount; this.remoteDevicesCount = remoteDevicesCount;
this.foldableState = foldableState;
} }
public @NonNull WebRtcViewModel.State getCallState() { public @NonNull WebRtcViewModel.State getCallState() {
@ -94,7 +98,10 @@ public final class CallParticipantsState {
listParticipants.addAll(remoteParticipants.getListParticipants()); listParticipants.addAll(remoteParticipants.getListParticipants());
} }
listParticipants.add(CallParticipant.EMPTY); if (foldableState.isFlat()) {
listParticipants.add(CallParticipant.EMPTY);
}
Collections.reverse(listParticipants); Collections.reverse(listParticipants);
return listParticipants; return listParticipants;
@ -155,6 +162,10 @@ public final class CallParticipantsState {
return localRenderState; return localRenderState;
} }
public boolean isFolded() {
return foldableState.isFolded();
}
public boolean isLargeVideoGroup() { public boolean isLargeVideoGroup() {
return getAllRemoteParticipants().size() > SMALL_GROUP_MAX; return getAllRemoteParticipants().size() > SMALL_GROUP_MAX;
} }
@ -215,7 +226,8 @@ public final class CallParticipantsState {
oldState.isInPipMode, oldState.isInPipMode,
newShowVideoForOutgoing, newShowVideoForOutgoing,
oldState.isViewingFocusedParticipant, oldState.isViewingFocusedParticipant,
webRtcViewModel.getRemoteDevicesCount()); webRtcViewModel.getRemoteDevicesCount(),
oldState.foldableState);
} }
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, boolean isInPip) { public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, boolean isInPip) {
@ -237,7 +249,8 @@ public final class CallParticipantsState {
isInPip, isInPip,
oldState.showVideoForOutgoing, oldState.showVideoForOutgoing,
oldState.isViewingFocusedParticipant, oldState.isViewingFocusedParticipant,
oldState.remoteDevicesCount); oldState.remoteDevicesCount,
oldState.foldableState);
} }
public static @NonNull CallParticipantsState setExpanded(@NonNull CallParticipantsState oldState, boolean expanded) { public static @NonNull CallParticipantsState setExpanded(@NonNull CallParticipantsState oldState, boolean expanded) {
@ -259,7 +272,8 @@ public final class CallParticipantsState {
oldState.isInPipMode, oldState.isInPipMode,
oldState.showVideoForOutgoing, oldState.showVideoForOutgoing,
oldState.isViewingFocusedParticipant, oldState.isViewingFocusedParticipant,
oldState.remoteDevicesCount); oldState.remoteDevicesCount,
oldState.foldableState);
} }
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) { public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) {
@ -281,7 +295,31 @@ public final class CallParticipantsState {
oldState.isInPipMode, oldState.isInPipMode,
oldState.showVideoForOutgoing, oldState.showVideoForOutgoing,
selectedPage == SelectedPage.FOCUSED, selectedPage == SelectedPage.FOCUSED,
oldState.remoteDevicesCount); oldState.remoteDevicesCount,
oldState.foldableState);
}
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull WebRtcControls.FoldableState foldableState) {
WebRtcLocalRenderState localRenderState = determineLocalRenderMode(oldState.localParticipant,
oldState.isInPipMode,
oldState.showVideoForOutgoing,
oldState.getGroupCallState().isNotIdle(),
oldState.callState,
oldState.getAllRemoteParticipants().size(),
oldState.isViewingFocusedParticipant,
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
return new CallParticipantsState(oldState.callState,
oldState.groupCallState,
oldState.remoteParticipants,
oldState.localParticipant,
oldState.focusedParticipant,
localRenderState,
oldState.isInPipMode,
oldState.showVideoForOutgoing,
oldState.isViewingFocusedParticipant,
oldState.remoteDevicesCount,
foldableState);
} }
private static @NonNull WebRtcLocalRenderState determineLocalRenderMode(@NonNull CallParticipant localParticipant, private static @NonNull WebRtcLocalRenderState determineLocalRenderMode(@NonNull CallParticipant localParticipant,

Wyświetl plik

@ -116,14 +116,16 @@ public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestu
} }
public void clearVerticalBoundaries() { public void clearVerticalBoundaries() {
setVerticalBoundaries(parent.getTop(), parent.getMeasuredHeight() + parent.getTop()); setTopVerticalBoundary(parent.getTop());
setBottomVerticalBoundary(parent.getMeasuredHeight() + parent.getTop());
} }
public void setVerticalBoundaries(int topBoundary, int bottomBoundary) { public void setTopVerticalBoundary(int topBoundary) {
extraPaddingTop = topBoundary - parent.getTop(); extraPaddingTop = topBoundary - parent.getTop();
extraPaddingBottom = parent.getMeasuredHeight() + parent.getTop() - bottomBoundary; }
adjustPip(); public void setBottomVerticalBoundary(int bottomBoundary) {
extraPaddingBottom = parent.getMeasuredHeight() + parent.getTop() - bottomBoundary;
} }
private boolean onGestureFinished(MotionEvent e) { private boolean onGestureFinished(MotionEvent e) {

Wyświetl plik

@ -64,6 +64,10 @@ class WebRtcCallParticipantsPage {
return isPortrait; return isPortrait;
} }
public @NonNull CallParticipantsLayout.LayoutStrategy getLayoutStrategy() {
return CallParticipantsLayoutStrategies.getStrategy(isPortrait);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

Wyświetl plik

@ -86,7 +86,7 @@ class WebRtcCallParticipantsPagerAdapter extends ListAdapter<WebRtcCallParticipa
@Override @Override
void bind(WebRtcCallParticipantsPage page) { void bind(WebRtcCallParticipantsPage page) {
callParticipantsLayout.update(page.getCallParticipants(), page.getFocusedParticipant(), page.isRenderInPip(), page.isPortrait()); callParticipantsLayout.update(page.getCallParticipants(), page.getFocusedParticipant(), page.isRenderInPip(), page.isPortrait(), page.getLayoutStrategy());
} }
} }

Wyświetl plik

@ -36,7 +36,6 @@ import com.google.android.material.button.MaterialButton;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.ResizeAnimation; import org.thoughtcrime.securesms.animation.ResizeAnimation;
import org.thoughtcrime.securesms.components.AccessibleToggleButton; import org.thoughtcrime.securesms.components.AccessibleToggleButton;
import org.thoughtcrime.securesms.components.sensors.Orientation;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.events.WebRtcViewModel;
@ -46,6 +45,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.CameraState; import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.util.BlurTransformation; import org.thoughtcrime.securesms.util.BlurTransformation;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SetUtil; import org.thoughtcrime.securesms.util.SetUtil;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.util.views.Stub;
@ -57,9 +57,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE; public class WebRtcCallView extends ConstraintLayout {
public class WebRtcCallView extends FrameLayout {
private static final long TRANSITION_DURATION_MILLIS = 250; private static final long TRANSITION_DURATION_MILLIS = 250;
private static final int SMALL_ONGOING_CALL_BUTTON_MARGIN_DP = 8; private static final int SMALL_ONGOING_CALL_BUTTON_MARGIN_DP = 8;
@ -102,12 +100,15 @@ public class WebRtcCallView extends FrameLayout {
private View errorButton; private View errorButton;
private int pagerBottomMarginDp; private int pagerBottomMarginDp;
private boolean controlsVisible = true; private boolean controlsVisible = true;
private Guideline topFoldGuideline;
private Guideline callScreenTopFoldGuideline;
private View foldParticipantCountWrapper;
private TextView foldParticipantCount;
private WebRtcCallParticipantsPagerAdapter pagerAdapter; private WebRtcCallParticipantsPagerAdapter pagerAdapter;
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter; private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
private PictureInPictureExpansionHelper pictureInPictureExpansionHelper; private PictureInPictureExpansionHelper pictureInPictureExpansionHelper;
private final Set<View> incomingCallViews = new HashSet<>(); private final Set<View> incomingCallViews = new HashSet<>();
private final Set<View> topViews = new HashSet<>(); private final Set<View> topViews = new HashSet<>();
private final Set<View> visibleViewSet = new HashSet<>(); private final Set<View> visibleViewSet = new HashSet<>();
@ -161,6 +162,10 @@ public class WebRtcCallView extends FrameLayout {
errorButton = findViewById(R.id.call_screen_error_cancel); errorButton = findViewById(R.id.call_screen_error_cancel);
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint)); groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view)); groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view));
topFoldGuideline = findViewById(R.id.fold_top_guideline);
callScreenTopFoldGuideline = findViewById(R.id.fold_top_call_screen_guideline);
foldParticipantCountWrapper = findViewById(R.id.fold_show_participants_menu_counter_wrapper);
foldParticipantCount = findViewById(R.id.fold_show_participants_menu_counter);
View topGradient = findViewById(R.id.call_screen_header_gradient); View topGradient = findViewById(R.id.call_screen_header_gradient);
View decline = findViewById(R.id.call_screen_decline_call); View decline = findViewById(R.id.call_screen_decline_call);
@ -279,10 +284,18 @@ public class WebRtcCallView extends FrameLayout {
@Override @Override
public void onWindowSystemUiVisibilityChanged(int visible) { public void onWindowSystemUiVisibilityChanged(int visible) {
if ((visible & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { if ((visible & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop()); if (controls.adjustForFold()) {
pictureInPictureGestureHelper.clearVerticalBoundaries();
pictureInPictureGestureHelper.setTopVerticalBoundary(toolbar.getBottom());
} else {
pictureInPictureGestureHelper.setTopVerticalBoundary(toolbar.getBottom());
pictureInPictureGestureHelper.setBottomVerticalBoundary(videoToggle.getTop());
}
} else { } else {
pictureInPictureGestureHelper.clearVerticalBoundaries(); pictureInPictureGestureHelper.clearVerticalBoundaries();
} }
pictureInPictureGestureHelper.adjustPip();
} }
@Override @Override
@ -323,16 +336,22 @@ public class WebRtcCallView extends FrameLayout {
} }
if (state.getGroupCallState().isNotIdle() && participantCount != null) { if (state.getGroupCallState().isNotIdle() && participantCount != null) {
participantCount.setText(state.getParticipantCount() String text = state.getParticipantCount()
.mapToObj(String::valueOf).orElse("\u2014")); .mapToObj(String::valueOf).orElse("\u2014");
participantCount.setEnabled(state.getParticipantCount().isPresent()); boolean enabled = state.getParticipantCount().isPresent();
participantCount.setText(text);
participantCount.setEnabled(enabled);
foldParticipantCount.setText(text);
foldParticipantCount.setEnabled(enabled);
} }
pagerAdapter.submitList(pages); pagerAdapter.submitList(pages);
recyclerAdapter.submitList(state.getListParticipants()); recyclerAdapter.submitList(state.getListParticipants());
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), state.getFocusedParticipant()); updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), state.getFocusedParticipant());
if (state.isLargeVideoGroup() && !state.isInPipMode()) { if (state.isLargeVideoGroup() && !state.isInPipMode() && !state.isFolded()) {
layoutParticipantsForLargeCount(); layoutParticipantsForLargeCount();
} else { } else {
layoutParticipantsForSmallCount(); layoutParticipantsForSmallCount();
@ -423,7 +442,9 @@ public class WebRtcCallView extends FrameLayout {
toolbar.inflateMenu(R.menu.group_call); toolbar.inflateMenu(R.menu.group_call);
View showParticipants = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list).getActionView(); View showParticipants = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list).getActionView();
showParticipants.setAlpha(0);
showParticipants.setOnClickListener(unused -> showParticipantsList()); showParticipants.setOnClickListener(unused -> showParticipantsList());
foldParticipantCountWrapper.setOnClickListener(unused -> showParticipantsList());
participantCount = showParticipants.findViewById(R.id.show_participants_menu_counter); participantCount = showParticipants.findViewById(R.id.show_participants_menu_counter);
} }
@ -480,6 +501,20 @@ public class WebRtcCallView extends FrameLayout {
visibleViewSet.clear(); visibleViewSet.clear();
if (webRtcControls.adjustForFold()) {
topFoldGuideline.setGuidelineEnd(webRtcControls.getFold());
callScreenTopFoldGuideline.setGuidelineEnd(webRtcControls.getFold());
if (webRtcControls.displayGroupMembersButton()) {
visibleViewSet.add(foldParticipantCountWrapper);
}
} else {
topFoldGuideline.setGuidelineEnd(0);
callScreenTopFoldGuideline.setGuidelineEnd(0);
}
setShowParticipantsState(webRtcControls.adjustForFold());
if (webRtcControls.displayStartCallControls()) { if (webRtcControls.displayStartCallControls()) {
visibleViewSet.add(footerGradient); visibleViewSet.add(footerGradient);
visibleViewSet.add(startCallControls); visibleViewSet.add(startCallControls);
@ -577,10 +612,15 @@ public class WebRtcCallView extends FrameLayout {
controls = webRtcControls; controls = webRtcControls;
if (!controls.isFadeOutEnabled()) {
controlsVisible = true;
}
if (!visibleViewSet.equals(lastVisibleSet) || !controls.isFadeOutEnabled()) { if (!visibleViewSet.equals(lastVisibleSet) || !controls.isFadeOutEnabled()) {
fadeInNewUiState(lastVisibleSet, webRtcControls.displaySmallOngoingCallButtons()); fadeInNewUiState(lastVisibleSet, webRtcControls.displaySmallOngoingCallButtons());
post(() -> pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop()));
} }
onWindowSystemUiVisibilityChanged(getWindowSystemUiVisibility());
} }
public @NonNull View getVideoTooltipTarget() { public @NonNull View getVideoTooltipTarget() {
@ -597,6 +637,18 @@ public class WebRtcCallView extends FrameLayout {
} }
} }
private void setShowParticipantsState(boolean isFolded) {
MenuItem item = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list);
if (item != null) {
View showParticipants = item.getActionView();
showParticipants.animate().alpha(isFolded ? 0f : 1f);
showParticipants.setClickable(!isFolded);
foldParticipantCountWrapper.animate().alpha(isFolded ? 1f : 0f);
foldParticipantCount.setClickable(isFolded);
}
}
private void expandPip(@NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) { private void expandPip(@NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
pictureInPictureExpansionHelper.expand(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() { pictureInPictureExpansionHelper.expand(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
@Override @Override
@ -760,6 +812,8 @@ public class WebRtcCallView extends FrameLayout {
constraintSet.setVisibility(view.getId(), visibility); constraintSet.setVisibility(view.getId(), visibility);
} }
adjustParticipantsRecycler(constraintSet);
constraintSet.applyTo(parent); constraintSet.applyTo(parent);
layoutParticipants(); layoutParticipants();
@ -789,9 +843,21 @@ public class WebRtcCallView extends FrameLayout {
} }
} }
adjustParticipantsRecycler(constraintSet);
constraintSet.applyTo(parent); constraintSet.applyTo(parent);
} }
private void adjustParticipantsRecycler(@NonNull ConstraintSet constraintSet) {
if (controlsVisible) {
constraintSet.connect(R.id.call_screen_participants_recycler, ConstraintSet.BOTTOM, R.id.call_screen_video_toggle, ConstraintSet.TOP);
} else {
constraintSet.connect(R.id.call_screen_participants_recycler, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM);
}
constraintSet.setHorizontalBias(R.id.call_screen_participants_recycler, controls.adjustForFold() ? 0.5f : 1f);
}
private void scheduleFadeOut() { private void scheduleFadeOut() {
cancelFadeOut(); cancelFadeOut();

Wyświetl plik

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.webrtc;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.Pair;
import androidx.annotation.MainThread; import androidx.annotation.MainThread;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -14,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.components.sensors.Orientation;
import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase;
@ -41,7 +43,9 @@ public class WebRtcCallViewModel extends ViewModel {
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true); private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false); private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE); private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls); private final MutableLiveData<WebRtcControls.FoldableState> foldableState = new MutableLiveData<>(WebRtcControls.FoldableState.flat());
private final LiveData<WebRtcControls> controlsWithFoldableState = LiveDataUtil.combineLatest(foldableState, webRtcControls, this::updateControlsFoldableState);
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, controlsWithFoldableState, this::getRealWebRtcControls);
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>(); private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L); private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live()); private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
@ -53,6 +57,8 @@ public class WebRtcCallViewModel extends ViewModel {
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1); private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1);
private final LiveData<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint); private final LiveData<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint);
private final LiveData<Orientation> orientation; private final LiveData<Orientation> orientation;
private final MutableLiveData<Boolean> isLandscapeEnabled = new MutableLiveData<>();
private final LiveData<Integer> controlsRotation;
private boolean canDisplayTooltipIfNeeded = true; private boolean canDisplayTooltipIfNeeded = true;
private boolean hasEnabledLocalVideo = false; private boolean hasEnabledLocalVideo = false;
@ -69,13 +75,24 @@ public class WebRtcCallViewModel extends ViewModel {
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication()); private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
private WebRtcCallViewModel(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) { private WebRtcCallViewModel(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) {
orientation = deviceOrientationMonitor.getOrientation(); orientation = deviceOrientationMonitor.getOrientation();
controlsRotation = LiveDataUtil.combineLatest(Transformations.distinctUntilChanged(isLandscapeEnabled),
Transformations.distinctUntilChanged(orientation),
this::resolveRotation);
}
public LiveData<Integer> getControlsRotation() {
return controlsRotation;
} }
public LiveData<Orientation> getOrientation() { public LiveData<Orientation> getOrientation() {
return Transformations.distinctUntilChanged(orientation); return Transformations.distinctUntilChanged(orientation);
} }
public LiveData<Pair<Orientation, Boolean>> getOrientationAndLandscapeEnabled() {
return LiveDataUtil.combineLatest(orientation, isLandscapeEnabled, Pair::new);
}
public LiveData<Boolean> getMicrophoneEnabled() { public LiveData<Boolean> getMicrophoneEnabled() {
return Transformations.distinctUntilChanged(microphoneEnabled); return Transformations.distinctUntilChanged(microphoneEnabled);
} }
@ -92,6 +109,12 @@ public class WebRtcCallViewModel extends ViewModel {
liveRecipient.setValue(recipient.live()); liveRecipient.setValue(recipient.live());
} }
public void setFoldableState(@NonNull WebRtcControls.FoldableState foldableState) {
this.foldableState.postValue(foldableState);
ThreadUtil.runOnMain(() -> participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), foldableState)));
}
public LiveData<Event> getEvents() { public LiveData<Event> getEvents() {
return events; return events;
} }
@ -140,6 +163,10 @@ public class WebRtcCallViewModel extends ViewModel {
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), isInPipMode)); participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), isInPipMode));
} }
public void setIsLandscapeEnabled(boolean isLandscapeEnabled) {
this.isLandscapeEnabled.postValue(isLandscapeEnabled);
}
@MainThread @MainThread
public void setIsViewingFocusedParticipant(@NonNull CallParticipantsState.SelectedPage page) { public void setIsViewingFocusedParticipant(@NonNull CallParticipantsState.SelectedPage page) {
if (page == CallParticipantsState.SelectedPage.FOCUSED) { if (page == CallParticipantsState.SelectedPage.FOCUSED) {
@ -239,6 +266,23 @@ public class WebRtcCallViewModel extends ViewModel {
} }
} }
private int resolveRotation(boolean isLandscapeEnabled, @NonNull Orientation orientation) {
if (isLandscapeEnabled) {
return 0;
}
switch (orientation) {
case LANDSCAPE_LEFT_EDGE:
return 90;
case LANDSCAPE_RIGHT_EDGE:
return -90;
case PORTRAIT_BOTTOM_EDGE:
return 0;
default:
throw new AssertionError();
}
}
private boolean containsPlaceholders(@NonNull List<CallParticipant> callParticipants) { private boolean containsPlaceholders(@NonNull List<CallParticipant> callParticipants) {
return Stream.of(callParticipants).anyMatch(p -> p.getCallParticipantId().getDemuxId() == CallParticipantId.DEFAULT_ID); return Stream.of(callParticipants).anyMatch(p -> p.getCallParticipantId().getDemuxId() == CallParticipantId.DEFAULT_ID);
} }
@ -314,7 +358,12 @@ public class WebRtcCallViewModel extends ViewModel {
callState, callState,
groupCallState, groupCallState,
audioOutput, audioOutput,
participantLimit)); participantLimit,
WebRtcControls.FoldableState.flat()));
}
private @NonNull WebRtcControls updateControlsFoldableState(@NonNull WebRtcControls.FoldableState foldableState, @NonNull WebRtcControls controls) {
return controls.withFoldableState(foldableState);
} }
private @NonNull WebRtcControls getRealWebRtcControls(boolean isInPipMode, @NonNull WebRtcControls controls) { private @NonNull WebRtcControls getRealWebRtcControls(boolean isInPipMode, @NonNull WebRtcControls controls) {

Wyświetl plik

@ -4,6 +4,7 @@ import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
@ -11,7 +12,7 @@ import org.thoughtcrime.securesms.R;
public final class WebRtcControls { public final class WebRtcControls {
public static final WebRtcControls NONE = new WebRtcControls(); public static final WebRtcControls NONE = new WebRtcControls();
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null); public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null, FoldableState.flat());
private final boolean isRemoteVideoEnabled; private final boolean isRemoteVideoEnabled;
private final boolean isLocalVideoEnabled; private final boolean isLocalVideoEnabled;
@ -23,9 +24,10 @@ public final class WebRtcControls {
private final GroupCallState groupCallState; private final GroupCallState groupCallState;
private final WebRtcAudioOutput audioOutput; private final WebRtcAudioOutput audioOutput;
private final Long participantLimit; private final Long participantLimit;
private final FoldableState foldableState;
private WebRtcControls() { private WebRtcControls() {
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null); this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null, FoldableState.flat());
} }
WebRtcControls(boolean isLocalVideoEnabled, WebRtcControls(boolean isLocalVideoEnabled,
@ -37,7 +39,8 @@ public final class WebRtcControls {
@NonNull CallState callState, @NonNull CallState callState,
@NonNull GroupCallState groupCallState, @NonNull GroupCallState groupCallState,
@NonNull WebRtcAudioOutput audioOutput, @NonNull WebRtcAudioOutput audioOutput,
@Nullable Long participantLimit) @Nullable Long participantLimit,
@NonNull FoldableState foldableState)
{ {
this.isLocalVideoEnabled = isLocalVideoEnabled; this.isLocalVideoEnabled = isLocalVideoEnabled;
this.isRemoteVideoEnabled = isRemoteVideoEnabled; this.isRemoteVideoEnabled = isRemoteVideoEnabled;
@ -49,6 +52,21 @@ public final class WebRtcControls {
this.groupCallState = groupCallState; this.groupCallState = groupCallState;
this.audioOutput = audioOutput; this.audioOutput = audioOutput;
this.participantLimit = participantLimit; this.participantLimit = participantLimit;
this.foldableState = foldableState;
}
public @NonNull WebRtcControls withFoldableState(FoldableState foldableState) {
return new WebRtcControls(isLocalVideoEnabled,
isRemoteVideoEnabled,
isMoreThanOneCameraAvailable,
isBluetoothAvailable,
isInPipMode,
hasAtLeastOneRemote,
callState,
groupCallState,
audioOutput,
participantLimit,
foldableState);
} }
boolean displayErrorControls() { boolean displayErrorControls() {
@ -59,6 +77,14 @@ public final class WebRtcControls {
return isPreJoin(); return isPreJoin();
} }
boolean adjustForFold() {
return foldableState.isFolded();
}
@Px int getFold() {
return foldableState.getFoldPoint();
}
@StringRes int getStartCallButtonText() { @StringRes int getStartCallButtonText() {
if (isGroupCall()) { if (isGroupCall()) {
if (groupCallState == GroupCallState.FULL) { if (groupCallState == GroupCallState.FULL) {
@ -130,7 +156,7 @@ public final class WebRtcControls {
} }
boolean isFadeOutEnabled() { boolean isFadeOutEnabled() {
return isAtLeastOutgoing() && isRemoteVideoEnabled; return isAtLeastOutgoing() && isRemoteVideoEnabled && foldableState.isFlat();
} }
boolean displaySmallOngoingCallButtons() { boolean displaySmallOngoingCallButtons() {
@ -199,4 +225,35 @@ public final class WebRtcControls {
return compareTo(other) >= 0; return compareTo(other) >= 0;
} }
} }
public static final class FoldableState {
private static final int NOT_SET = -1;
private final int foldPoint;
public FoldableState(int foldPoint) {
this.foldPoint = foldPoint;
}
public boolean isFolded() {
return foldPoint != NOT_SET;
}
public boolean isFlat() {
return foldPoint == NOT_SET;
}
public int getFoldPoint() {
return foldPoint;
}
public static @NonNull FoldableState folded(int foldPoint) {
return new FoldableState(foldPoint);
}
public static @NonNull FoldableState flat() {
return new FoldableState(NOT_SET);
}
}
} }

Wyświetl plik

@ -1,7 +1,9 @@
package org.thoughtcrime.securesms.messagerequests; package org.thoughtcrime.securesms.messagerequests;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@ -44,11 +46,17 @@ public class CalleeMustAcceptMessageRequestActivity extends BaseActivity {
return intent; return intent;
} }
@SuppressLint("SourceLockedOrientationActivity")
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.callee_must_accept_message_request_dialog_fragment); setContentView(R.layout.callee_must_accept_message_request_dialog_fragment);
boolean callingFixedToPortrait = getResources().getConfiguration().densityDpi < 480;
if (callingFixedToPortrait) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
description = findViewById(R.id.description); description = findViewById(R.id.description);
avatar = findViewById(R.id.avatar); avatar = findViewById(R.id.avatar);
okay = findViewById(R.id.okay); okay = findViewById(R.id.okay);

Wyświetl plik

@ -168,8 +168,8 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
process((s, p) -> p.handleUpdateRenderedResolutions(s)); process((s, p) -> p.handleUpdateRenderedResolutions(s));
} }
public void orientationChanged(int degrees) { public void orientationChanged(boolean isLandscapeEnabled, int degrees) {
process((s, p) -> p.handleOrientationChanged(s, degrees)); process((s, p) -> p.handleOrientationChanged(s, isLandscapeEnabled, degrees));
} }
public void setAudioSpeaker(boolean isSpeaker) { public void setAudioSpeaker(boolean isSpeaker) {
@ -649,7 +649,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
@Override @Override
public void onFullyInitialized() { public void onFullyInitialized() {
process((s, p) -> p.handleOrientationChanged(s, s.getLocalDeviceState().getOrientation().getDegrees())); process((s, p) -> p.handleOrientationChanged(s, false, s.getLocalDeviceState().getOrientation().getDegrees()));
} }
@Override @Override

Wyświetl plik

@ -11,6 +11,7 @@ import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId; import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager; import org.signal.ringrtc.CallManager;
import org.signal.ringrtc.GroupCall; import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.sensors.Orientation; import org.thoughtcrime.securesms.components.sensors.Orientation;
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
@ -454,24 +455,27 @@ public abstract class WebRtcActionProcessor {
return currentState; return currentState;
} }
protected @NonNull WebRtcServiceState handleOrientationChanged(@NonNull WebRtcServiceState currentState, int orientationDegrees) { protected @NonNull WebRtcServiceState handleOrientationChanged(@NonNull WebRtcServiceState currentState, boolean isLandscapeEnabled, int orientationDegrees) {
Camera camera = currentState.getVideoState().getCamera(); Camera camera = currentState.getVideoState().getCamera();
if (camera != null) { if (camera != null) {
camera.setOrientation(orientationDegrees); camera.setOrientation(orientationDegrees);
} }
int sinkRotationDegrees = isLandscapeEnabled ? BroadcastVideoSink.DEVICE_ROTATION_IGNORE : orientationDegrees;
int stateRotationDegrees = isLandscapeEnabled ? 0 : orientationDegrees;
BroadcastVideoSink sink = currentState.getVideoState().getLocalSink(); BroadcastVideoSink sink = currentState.getVideoState().getLocalSink();
if (sink != null) { if (sink != null) {
sink.setDeviceOrientationDegrees(orientationDegrees); sink.setDeviceOrientationDegrees(sinkRotationDegrees);
} }
for (CallParticipant callParticipant : currentState.getCallInfoState().getRemoteCallParticipants()) { for (CallParticipant callParticipant : currentState.getCallInfoState().getRemoteCallParticipants()) {
callParticipant.getVideoSink().setDeviceOrientationDegrees(orientationDegrees); callParticipant.getVideoSink().setDeviceOrientationDegrees(sinkRotationDegrees);
} }
return currentState.builder() return currentState.builder()
.changeLocalDeviceState() .changeLocalDeviceState()
.setOrientation(Orientation.fromDegrees(orientationDegrees)) .setOrientation(Orientation.fromDegrees(stateRotationDegrees))
.build(); .build();
} }

Wyświetl plik

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:type="linear"
android:angle="90"
android:startColor="@color/transparent_black_60" />
</shape>

Wyświetl plik

@ -4,10 +4,21 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:parentTag="org.thoughtcrime.securesms.components.webrtc.WebRtcCallView"> tools:parentTag="org.thoughtcrime.securesms.components.webrtc.WebRtcCallView">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/fold_top_guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_end="0dp" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/call_screen_participants_parent" android:id="@+id/call_screen_participants_parent"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent"> android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@id/fold_top_guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/call_screen_participants_pager" android:id="@+id/call_screen_participants_pager"
@ -21,22 +32,64 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<include <FrameLayout
layout="@layout/webrtc_call_view_large_local_render" android:id="@+id/call_screen_large_local_renderer_frame"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" /> android:layout_height="0dp"
android:background="@color/black"
app:layout_constraintBottom_toTopOf="@id/fold_top_guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<org.thoughtcrime.securesms.components.webrtc.TextureViewRenderer
android:id="@+id/call_screen_large_local_renderer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/call_screen_large_local_video_off_avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/call_screen_large_local_video_off"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/WebRtcCallView__your_video_is_off"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:textColor="@color/white"
android:visibility="gone"
app:drawableTopCompat="@drawable/ic_video_off_solid_white_28"
tools:visibility="visible" />
</FrameLayout>
<ViewStub <ViewStub
android:id="@+id/group_call_call_full_view" android:id="@+id/group_call_call_full_view"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout="@layout/group_call_call_full" /> android:layout="@layout/group_call_call_full"
app:layout_constraintBottom_toTopOf="@id/fold_top_guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/call_screen" android:id="@+id/call_screen"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/fold_top_call_screen_guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toTopOf="@id/call_screen_navigation_bar_guideline"
app:layout_constraintGuide_end="0dp" />
<View <View
android:id="@+id/call_screen_header_gradient" android:id="@+id/call_screen_header_gradient"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -68,19 +121,20 @@
<View <View
android:id="@+id/call_screen_footer_gradient" android:id="@+id/call_screen_footer_gradient"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="-40dp" android:layout_margin="-40dp"
android:background="@drawable/webrtc_call_screen_header_gradient" android:background="@drawable/webrtc_call_screen_footer_gradient"
android:rotation="180"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/call_screen_footer_gradient_spacer" app:layout_constraintTop_toTopOf="@id/call_screen_footer_gradient_spacer"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/call_screen_participants_recycler" android:id="@+id/call_screen_participants_recycler"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="72dp" android:layout_height="72dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
@ -89,14 +143,19 @@
android:visibility="gone" android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@id/call_screen_video_toggle" app:layout_constraintBottom_toTopOf="@id/call_screen_video_toggle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toStartOf="parent"
app:reverseLayout="true" app:reverseLayout="true"
tools:listitem="@layout/webrtc_call_participant_recycler_item" /> tools:itemCount="2"
tools:listitem="@layout/webrtc_call_participant_recycler_item"
tools:visibility="visible" />
<org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout <org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout
android:id="@+id/call_screen_pip_area" android:id="@+id/call_screen_pip_area"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/call_screen_navigation_bar_guideline" app:layout_constraintBottom_toTopOf="@+id/fold_top_call_screen_guideline"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -358,6 +417,39 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<LinearLayout
android:id="@+id/fold_show_participants_menu_counter_wrapper"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:alpha="0"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:minWidth="48dp"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/fold_top_call_screen_guideline"
tools:background="@color/core_ultramarine">
<androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
app:srcCompat="@drawable/ic_group_solid_24"
app:tint="@color/core_white" />
<TextView
android:id="@+id/fold_show_participants_menu_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/core_white"
android:textSize="13sp"
android:textStyle="bold"
tools:text="0" />
</LinearLayout>
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/call_screen_footer_gradient_barrier" android:id="@+id/call_screen_footer_gradient_barrier"
android:layout_width="wrap_content" android:layout_width="wrap_content"

Wyświetl plik

@ -231,6 +231,9 @@ dependencyVerification {
['androidx.viewpager:viewpager:1.0.0', ['androidx.viewpager:viewpager:1.0.0',
'147af4e14a1984010d8f155e5e19d781f03c1d70dfed02a8e0d18428b8fc8682'], '147af4e14a1984010d8f155e5e19d781f03c1d70dfed02a8e0d18428b8fc8682'],
['androidx.window:window:1.0.0-alpha09',
'edbaafc3a7b0977c138916585d761fe159a521177a30065d676d067edf6571d2'],
['cn.carbswang.android:NumberPickerView:1.0.9', ['cn.carbswang.android:NumberPickerView:1.0.9',
'18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729'], '18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729'],
@ -507,20 +510,23 @@ dependencyVerification {
['org.jetbrains.kotlin:kotlin-reflect:1.4.10', ['org.jetbrains.kotlin:kotlin-reflect:1.4.10',
'3ab3413ec945f801448360ad97bc6e14fec6d606889ede3c707cc277b4467f45'], '3ab3413ec945f801448360ad97bc6e14fec6d606889ede3c707cc277b4467f45'],
['org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32', ['org.jetbrains.kotlin:kotlin-stdlib-common:1.5.10',
'e1ff6f55ee9e7591dcc633f7757bac25a7edb1cc7f738b37ec652f10f66a4145'], 'd958ce94beda85f865829fb95012804866db7d5246615fd71a2f5aabca3e7994'],
['org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32', ['org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0',
'5f801e75ca27d8791c14b07943c608da27620d910a8093022af57f543d5d98b6'], 'ac12f092f12b575c1f9e0ab5025b1e610b0fe95663e26371c16c328895711bae'],
['org.jetbrains.kotlin:kotlin-stdlib:1.4.32', ['org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0',
'13e9fd3e69dc7230ce0fc873a92a4e5d521d179bcf1bef75a6705baac3bfecba'], '15e6c81b9e845eefe58d51a04670bb90418046f458264ec0e61ee9bdbc1bfae7'],
['org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1', ['org.jetbrains.kotlin:kotlin-stdlib:1.5.10',
'd4cadb673b2101f1ee5fbc147956ac78b1cfd9cc255fb53d3aeb88dff11d99ca'], 'ca87c454cd3f2e60931f1803c59699d510d3b4b959cd7119296fb947581d722d'],
['org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.1', ['org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0',
'6d2f87764b6638f27aff12ed380db4b63c9d46ba55dc32683a650598fa5a3e22'], '7099198391d673c199fea084423d9f3fdc79470acba19111330c7f88504279c7'],
['org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0',
'78d6cc7135f84d692ff3752fcfd1fa1bbe0940d7df70652e4f1eaeec0c78afbb'],
['org.jetbrains:annotations:13.0', ['org.jetbrains:annotations:13.0',
'ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478'], 'ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478'],