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') {
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'

Wyświetl plik

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

Wyświetl plik

@ -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"/>
<meta-data android:name="android.supports_size_changes"
android:value="true" />
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
@ -117,16 +121,15 @@
<activity android:name=".WebRtcCallActivity"
android:theme="@style/TextSecure.DarkTheme.WebRTCCall"
android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity=".calling"
android:resizeableActivity="true"
android:launchMode="singleTask"/>
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:screenOrientation="portrait"
android:noHistory="true"
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.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<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 static final int DEVICE_ROTATION_IGNORE = -1;
private final EglBase eglBase;
private final WeakHashMap<VideoSink, Boolean> sinks;
private final WeakHashMap<Object, Point> requestingSizes;
@ -85,7 +87,10 @@ public class BroadcastVideoSink implements VideoSink {
@Override
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();
if (rotation > 0) {
rotation += rotateWithDevice ? videoFrame.getRotation() : 0;

Wyświetl plik

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

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -86,7 +86,7 @@ class WebRtcCallParticipantsPagerAdapter extends ListAdapter<WebRtcCallParticipa
@Override
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.animation.ResizeAnimation;
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
import org.thoughtcrime.securesms.components.sensors.Orientation;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.events.CallParticipant;
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.ringrtc.CameraState;
import org.thoughtcrime.securesms.util.BlurTransformation;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SetUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
@ -57,9 +57,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
public class WebRtcCallView extends FrameLayout {
public class WebRtcCallView extends ConstraintLayout {
private static final long TRANSITION_DURATION_MILLIS = 250;
private static final int SMALL_ONGOING_CALL_BUTTON_MARGIN_DP = 8;
@ -102,12 +100,15 @@ public class WebRtcCallView extends FrameLayout {
private View errorButton;
private int pagerBottomMarginDp;
private boolean controlsVisible = true;
private Guideline topFoldGuideline;
private Guideline callScreenTopFoldGuideline;
private View foldParticipantCountWrapper;
private TextView foldParticipantCount;
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
private PictureInPictureExpansionHelper pictureInPictureExpansionHelper;
private final Set<View> incomingCallViews = new HashSet<>();
private final Set<View> topViews = 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);
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
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 decline = findViewById(R.id.call_screen_decline_call);
@ -279,10 +284,18 @@ public class WebRtcCallView extends FrameLayout {
@Override
public void onWindowSystemUiVisibilityChanged(int visible) {
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 {
pictureInPictureGestureHelper.clearVerticalBoundaries();
}
pictureInPictureGestureHelper.adjustPip();
}
@Override
@ -323,16 +336,22 @@ public class WebRtcCallView extends FrameLayout {
}
if (state.getGroupCallState().isNotIdle() && participantCount != null) {
participantCount.setText(state.getParticipantCount()
.mapToObj(String::valueOf).orElse("\u2014"));
participantCount.setEnabled(state.getParticipantCount().isPresent());
String text = state.getParticipantCount()
.mapToObj(String::valueOf).orElse("\u2014");
boolean enabled = state.getParticipantCount().isPresent();
participantCount.setText(text);
participantCount.setEnabled(enabled);
foldParticipantCount.setText(text);
foldParticipantCount.setEnabled(enabled);
}
pagerAdapter.submitList(pages);
recyclerAdapter.submitList(state.getListParticipants());
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), state.getFocusedParticipant());
if (state.isLargeVideoGroup() && !state.isInPipMode()) {
if (state.isLargeVideoGroup() && !state.isInPipMode() && !state.isFolded()) {
layoutParticipantsForLargeCount();
} else {
layoutParticipantsForSmallCount();
@ -423,7 +442,9 @@ public class WebRtcCallView extends FrameLayout {
toolbar.inflateMenu(R.menu.group_call);
View showParticipants = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list).getActionView();
showParticipants.setAlpha(0);
showParticipants.setOnClickListener(unused -> showParticipantsList());
foldParticipantCountWrapper.setOnClickListener(unused -> showParticipantsList());
participantCount = showParticipants.findViewById(R.id.show_participants_menu_counter);
}
@ -480,6 +501,20 @@ public class WebRtcCallView extends FrameLayout {
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()) {
visibleViewSet.add(footerGradient);
visibleViewSet.add(startCallControls);
@ -577,10 +612,15 @@ public class WebRtcCallView extends FrameLayout {
controls = webRtcControls;
if (!controls.isFadeOutEnabled()) {
controlsVisible = true;
}
if (!visibleViewSet.equals(lastVisibleSet) || !controls.isFadeOutEnabled()) {
fadeInNewUiState(lastVisibleSet, webRtcControls.displaySmallOngoingCallButtons());
post(() -> pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop()));
}
onWindowSystemUiVisibilityChanged(getWindowSystemUiVisibility());
}
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) {
pictureInPictureExpansionHelper.expand(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
@Override
@ -760,6 +812,8 @@ public class WebRtcCallView extends FrameLayout {
constraintSet.setVisibility(view.getId(), visibility);
}
adjustParticipantsRecycler(constraintSet);
constraintSet.applyTo(parent);
layoutParticipants();
@ -789,9 +843,21 @@ public class WebRtcCallView extends FrameLayout {
}
}
adjustParticipantsRecycler(constraintSet);
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() {
cancelFadeOut();

Wyświetl plik

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.webrtc;
import android.os.Handler;
import android.os.Looper;
import android.util.Pair;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
@ -14,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
import org.thoughtcrime.securesms.components.sensors.Orientation;
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> isInPipMode = new MutableLiveData<>(false);
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 MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
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<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint);
private final LiveData<Orientation> orientation;
private final MutableLiveData<Boolean> isLandscapeEnabled = new MutableLiveData<>();
private final LiveData<Integer> controlsRotation;
private boolean canDisplayTooltipIfNeeded = true;
private boolean hasEnabledLocalVideo = false;
@ -69,13 +75,24 @@ public class WebRtcCallViewModel extends ViewModel {
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
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() {
return Transformations.distinctUntilChanged(orientation);
}
public LiveData<Pair<Orientation, Boolean>> getOrientationAndLandscapeEnabled() {
return LiveDataUtil.combineLatest(orientation, isLandscapeEnabled, Pair::new);
}
public LiveData<Boolean> getMicrophoneEnabled() {
return Transformations.distinctUntilChanged(microphoneEnabled);
}
@ -92,6 +109,12 @@ public class WebRtcCallViewModel extends ViewModel {
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() {
return events;
}
@ -140,6 +163,10 @@ public class WebRtcCallViewModel extends ViewModel {
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), isInPipMode));
}
public void setIsLandscapeEnabled(boolean isLandscapeEnabled) {
this.isLandscapeEnabled.postValue(isLandscapeEnabled);
}
@MainThread
public void setIsViewingFocusedParticipant(@NonNull CallParticipantsState.SelectedPage page) {
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) {
return Stream.of(callParticipants).anyMatch(p -> p.getCallParticipantId().getDemuxId() == CallParticipantId.DEFAULT_ID);
}
@ -314,7 +358,12 @@ public class WebRtcCallViewModel extends ViewModel {
callState,
groupCallState,
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) {

Wyświetl plik

@ -4,6 +4,7 @@ import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.StringRes;
import org.thoughtcrime.securesms.R;
@ -11,7 +12,7 @@ import org.thoughtcrime.securesms.R;
public final class 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 isLocalVideoEnabled;
@ -23,9 +24,10 @@ public final class WebRtcControls {
private final GroupCallState groupCallState;
private final WebRtcAudioOutput audioOutput;
private final Long participantLimit;
private final FoldableState foldableState;
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,
@ -37,7 +39,8 @@ public final class WebRtcControls {
@NonNull CallState callState,
@NonNull GroupCallState groupCallState,
@NonNull WebRtcAudioOutput audioOutput,
@Nullable Long participantLimit)
@Nullable Long participantLimit,
@NonNull FoldableState foldableState)
{
this.isLocalVideoEnabled = isLocalVideoEnabled;
this.isRemoteVideoEnabled = isRemoteVideoEnabled;
@ -49,6 +52,21 @@ public final class WebRtcControls {
this.groupCallState = groupCallState;
this.audioOutput = audioOutput;
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() {
@ -59,6 +77,14 @@ public final class WebRtcControls {
return isPreJoin();
}
boolean adjustForFold() {
return foldableState.isFolded();
}
@Px int getFold() {
return foldableState.getFoldPoint();
}
@StringRes int getStartCallButtonText() {
if (isGroupCall()) {
if (groupCallState == GroupCallState.FULL) {
@ -130,7 +156,7 @@ public final class WebRtcControls {
}
boolean isFadeOutEnabled() {
return isAtLeastOutgoing() && isRemoteVideoEnabled;
return isAtLeastOutgoing() && isRemoteVideoEnabled && foldableState.isFlat();
}
boolean displaySmallOngoingCallButtons() {
@ -199,4 +225,35 @@ public final class WebRtcControls {
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;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -44,11 +46,17 @@ public class CalleeMustAcceptMessageRequestActivity extends BaseActivity {
return intent;
}
@SuppressLint("SourceLockedOrientationActivity")
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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);
avatar = findViewById(R.id.avatar);
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));
}
public void orientationChanged(int degrees) {
process((s, p) -> p.handleOrientationChanged(s, degrees));
public void orientationChanged(boolean isLandscapeEnabled, int degrees) {
process((s, p) -> p.handleOrientationChanged(s, isLandscapeEnabled, degrees));
}
public void setAudioSpeaker(boolean isSpeaker) {
@ -649,7 +649,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
@Override
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

Wyświetl plik

@ -11,6 +11,7 @@ import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.sensors.Orientation;
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
@ -454,24 +455,27 @@ public abstract class WebRtcActionProcessor {
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();
if (camera != null) {
camera.setOrientation(orientationDegrees);
}
int sinkRotationDegrees = isLandscapeEnabled ? BroadcastVideoSink.DEVICE_ROTATION_IGNORE : orientationDegrees;
int stateRotationDegrees = isLandscapeEnabled ? 0 : orientationDegrees;
BroadcastVideoSink sink = currentState.getVideoState().getLocalSink();
if (sink != null) {
sink.setDeviceOrientationDegrees(orientationDegrees);
sink.setDeviceOrientationDegrees(sinkRotationDegrees);
}
for (CallParticipant callParticipant : currentState.getCallInfoState().getRemoteCallParticipants()) {
callParticipant.getVideoSink().setDeviceOrientationDegrees(orientationDegrees);
callParticipant.getVideoSink().setDeviceOrientationDegrees(sinkRotationDegrees);
}
return currentState.builder()
.changeLocalDeviceState()
.setOrientation(Orientation.fromDegrees(orientationDegrees))
.setOrientation(Orientation.fromDegrees(stateRotationDegrees))
.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"
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
android:id="@+id/call_screen_participants_parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="0dp"
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
android:id="@+id/call_screen_participants_pager"
@ -21,22 +32,64 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<include
layout="@layout/webrtc_call_view_large_local_render"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/call_screen_large_local_renderer_frame"
android:layout_width="0dp"
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
android:id="@+id/group_call_call_full_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/group_call_call_full" />
android:layout_width="0dp"
android:layout_height="0dp"
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
android:id="@+id/call_screen"
android:layout_width="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
android:id="@+id/call_screen_header_gradient"
android:layout_width="match_parent"
@ -68,19 +121,20 @@
<View
android:id="@+id/call_screen_footer_gradient"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="-40dp"
android:background="@drawable/webrtc_call_screen_header_gradient"
android:rotation="180"
android:background="@drawable/webrtc_call_screen_footer_gradient"
android:visibility="gone"
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"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/call_screen_participants_recycler"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="72dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
@ -89,14 +143,19 @@
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
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"
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
android:id="@+id/call_screen_pip_area"
android:layout_width="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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@ -358,6 +417,39 @@
app:layout_constraintEnd_toEndOf="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
android:id="@+id/call_screen_footer_gradient_barrier"
android:layout_width="wrap_content"

Wyświetl plik

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