Implement new video call experience.

This commit is contained in:
Alex Hart 2019-12-03 16:26:05 -04:00 committed by Greyson Parrelli
parent 2ada7f87f2
commit a8d826020d
34 changed files with 360 additions and 151 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 B

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M21.11,16.608a26.24,26.24 0,0 1,-4.513 -2.5,1.384 1.384,0 0,0 -1.918,0.284 9.116,9.116 0,0 0,-0.866 1.465,4.5 4.5,0 0,0 -0.357,1.242A13.113,13.113 0,0 1,6.9 10.542a4.474,4.474 0,0 0,1.243 -0.355,9.019 9.019,0 0,0 1.343,-0.779 1.444,1.444 0,0 0,0.4 -2A26.2,26.2 0,0 1,7.357 2.9a1.42,1.42 0,0 0,-1.71 -0.825,8.63 8.63,0 0,0 -1.1,0.421 4.284,4.284 0,0 0,-2.5 4.392l-0.014,0A16.948,16.948 0,0 0,17.073 21.953l0,-0.016a4.308,4.308 0,0 0,4.441 -2.492,8.732 8.732,0 0,0 0.431,-1.13A1.42,1.42 0,0 0,21.11 16.608Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M21.94,18.315a8.732,8.732 0,0 1,-0.431 1.13,4.308 4.308,0 0,1 -4.441,2.492l0,0.016A16.948,16.948 0,0 1,2.035 6.878l0.014,0a4.284,4.284 0,0 1,2.5 -4.392,8.63 8.63,0 0,1 1.1,-0.421 1.42,1.42 0,0 1,1.71 0.825A26.2,26.2 0,0 0,9.877 7.41a1.444,1.444 0,0 1,-0.4 2,9.019 9.019,0 0,1 -1.343,0.779 4.474,4.474 0,0 1,-1.243 0.355,13.113 13.113,0 0,0 6.56,6.557 4.5,4.5 0,0 1,0.357 -1.242,9.116 9.116,0 0,1 0.866,-1.465 1.384,1.384 0,0 1,1.918 -0.284,26.24 26.24,0 0,0 4.513,2.5A1.42,1.42 0,0 1,21.94 18.315ZM19.75,1A2.75,2.75 0,0 0,17 3.75V5H14.5A1.5,1.5 0,0 0,13 6.5v3A1.5,1.5 0,0 0,14.5 11h3.75a1.5,1.5 0,0 0,1.5 -1.5v-3A1.5,1.5 0,0 0,18.5 5.026V3.75a1.25,1.25 0,0 1,2.5 0V5h1.5V3.75A2.751,2.751 0,0 0,19.75 1Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M23,8.563v6.874a1,1 0,0 1,-1.419 0.908L18.5,14.923V9.077l3.081,-1.422A1,1 0,0 1,23 8.563ZM2.535,4.876A2.639,2.639 0,0 0,1.4 5.939,3.854 3.854,0 0,0 1,8.105V15.9a3.854,3.854 0,0 0,0.4 2.166,2.639 2.639,0 0,0 1.134,1.063 4.6,4.6 0,0 0,2.311 0.376h8.308a4.6,4.6 0,0 0,2.311 -0.376A2.639,2.639 0,0 0,16.6 18.061,3.854 3.854,0 0,0 17,15.9V8.105a3.854,3.854 0,0 0,-0.4 -2.166,2.639 2.639,0 0,0 -1.134,-1.063A4.6,4.6 0,0 0,13.154 4.5H4.846A4.6,4.6 0,0 0,2.535 4.876Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?icon_tint"
android:pathData="M23,8.563v6.874a1,1 0,0 1,-1.419 0.908L18.5,14.923V9.077l3.081,-1.422A1,1 0,0 1,23 8.563ZM2.535,4.876A2.639,2.639 0,0 0,1.4 5.939,3.854 3.854,0 0,0 1,8.105V15.9a3.854,3.854 0,0 0,0.4 2.166,2.639 2.639,0 0,0 1.134,1.063 4.6,4.6 0,0 0,2.311 0.376h8.308a4.6,4.6 0,0 0,2.311 -0.376A2.639,2.639 0,0 0,16.6 18.061,3.854 3.854,0 0,0 17,15.9V8.105a3.854,3.854 0,0 0,-0.4 -2.166,2.639 2.639,0 0,0 -1.134,-1.063A4.6,4.6 0,0 0,13.154 4.5H4.846A4.6,4.6 0,0 0,2.535 4.876Z"/>
</vector>

View File

@ -5,5 +5,5 @@
android:left="5dp"
android:right="5dp"
android:bottom="5dp"
android:drawable="@drawable/ic_videocam_white_24dp"/>
android:drawable="@drawable/ic_video_solid_24"/>
</layer-list>

View File

@ -53,14 +53,15 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
<include layout="@layout/expiration_timer_badge" />
<ImageView
android:id="@+id/verified_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_width="12dp"
android:layout_height="match_parent"
android:layout_marginEnd="3dp"
android:alpha="0.7"
android:src="@drawable/ic_check_circle_white_18dp"
android:tint="?conversation_subtitle_color"
app:srcCompat="@drawable/ic_check_24"
android:visibility="gone"
tools:visibility="visible" />
@ -74,6 +75,7 @@
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?conversation_subtitle_color"
android:text="@string/ConversationTitleView_verified"
android:textDirection="ltr" />
@ -85,6 +87,7 @@
android:layout_gravity="center_vertical|start"
android:ellipsize="end"
android:gravity="center_vertical"
android:textColor="?conversation_subtitle_color"
android:maxLines="1"
android:textDirection="ltr"
tools:text="(123) 123-1234" />

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/expiration_badge_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="11dp"
android:gravity="center"
android:visibility="gone"
android:orientation="horizontal">
<ImageView
android:id="@+id/menu_badge_icon"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="center"
android:layout_marginEnd="3dp"
android:scaleType="fitCenter"
android:tint="?conversation_subtitle_color"
app:srcCompat="@drawable/ic_timer_60_12" />
<TextView
android:id="@+id/expiration_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:gravity="center_horizontal|bottom"
android:paddingTop="1dp"
android:paddingBottom="3dp"
android:textColor="?conversation_subtitle_color"
android:textSize="14sp"
tools:text="1w" />
</LinearLayout>

View File

@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="?android:attr/actionButtonStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:gravity="center">
<ImageView
android:id="@+id/menu_badge_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
app:srcCompat="@drawable/ic_timer_conversation_24"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/expiration_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:gravity="center_horizontal|bottom"
android:paddingBottom="3dp"
android:paddingTop="1dp"
android:textColor="?conversation_subtitle_color"
android:textSize="10sp" />
</FrameLayout>

View File

@ -15,7 +15,7 @@
android:layout_height="24dp"
android:layout_margin="16dp"
android:tint="@color/grey_600"
tools:src="@drawable/ic_call_white_24dp" />
tools:src="@drawable/ic_phone_right_unlock_solid_24" />
<ImageView
android:id="@+id/contact_field_avatar"

View File

@ -6,20 +6,30 @@
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="?attr/message_icon"
android:layout_marginEnd="20dp"/>
<ImageView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
app:srcCompat="?attr/message_icon" />
<ImageView android:id="@+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_call_white_24dp"/>
<ImageView
android:id="@+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_phone_right_unlock_solid_24" />
<ImageView android:id="@+id/secure_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_call_secure_white_24dp"/>
<ImageView
android:id="@+id/secure_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
app:srcCompat="@drawable/ic_video_solid_24" />
<ImageView
android:id="@+id/secure_call"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_phone_right_solid_24" />
</LinearLayout>

View File

@ -26,6 +26,12 @@
android:layout_height="match_parent"
tools:visibility="invisible" />
<org.thoughtcrime.securesms.components.webrtc.PercentFrameLayout
android:id="@+id/local_large_render_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:visibility="invisible" />
<!-- "Call info" block #1, for the foreground call. -->
<RelativeLayout
android:id="@+id/call_info_1"

View File

@ -2,9 +2,14 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:title="@string/conversation_callable_secure__menu_video"
android:id="@+id/menu_video_secure"
android:icon="@drawable/ic_video_solid_24"
app:showAsAction="always" />
<item android:title="@string/conversation_callable_secure__menu_call"
android:id="@+id/menu_call_secure"
android:icon="@drawable/ic_call_secure_white_24dp"
android:icon="@drawable/ic_phone_right_solid_24"
app:showAsAction="always" />
</menu>

View File

@ -3,8 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_expiring_messages"
app:actionLayout="@layout/expiration_timer_menu"
app:showAsAction="always"
android:title="@string/menu_conversation_expiring_on__messages_expiring"/>
app:showAsAction="never"
android:title="@string/conversation_expiring_off__disappearing_messages"/>
</menu>

View File

@ -120,6 +120,10 @@
<!-- CommunicationActions -->
<string name="CommunicationActions_no_browser_found">No web browser found.</string>
<string name="CommunicationActions_a_cellular_call_is_already_in_progress">A cellular call is already in progress.</string>
<string name="CommunicationActions_start_video_call">Start video call?</string>
<string name="CommunicationActions_start_voice_call">Start voice call?</string>
<string name="CommunicationActions_cancel">Cancel</string>
<string name="CommunicationActions_call">Call</string>
<!-- ConfirmIdentityDialog -->
<string name="ConfirmIdentityDialog_your_safety_number_with_s_has_changed">Your safety number with %1$s has changed. This could either mean that someone is trying to intercept your communication, or that %2$s simply reinstalled Signal.</string>
@ -1453,6 +1457,7 @@
<!-- conversation_callable_secure -->
<string name="conversation_callable_secure__menu_call">Signal call</string>
<string name="conversation_callable_secure__menu_video">Signal video call</string>
<!-- conversation_context -->
<string name="conversation_context__menu_message_details">Message details</string>

View File

@ -302,7 +302,7 @@
<item name="menu_new_conversation_icon">@drawable/ic_add_white_24dp</item>
<item name="menu_group_icon">@drawable/ic_group_solid_24</item>
<item name="menu_search_icon">@drawable/ic_search_24</item>
<item name="menu_call_icon">@drawable/ic_call_white_24dp</item>
<item name="menu_call_icon">@drawable/ic_phone_right_unlock_solid_24</item>
<item name="menu_popup_expand">@drawable/ic_launch_white_24dp</item>
<item name="menu_unlock_icon">@drawable/ic_unlocked_white_24dp</item>
<item name="menu_lock_icon">@drawable/ic_lock_white_24dp</item>
@ -532,7 +532,7 @@
<item name="menu_new_conversation_icon">@drawable/ic_add_white_24dp</item>
<item name="menu_group_icon">@drawable/ic_group_solid_24</item>
<item name="menu_search_icon">@drawable/ic_search_24</item>
<item name="menu_call_icon">@drawable/ic_call_white_24dp</item>
<item name="menu_call_icon">@drawable/ic_phone_right_unlock_solid_24</item>
<item name="menu_popup_expand">@drawable/ic_launch_white_24dp</item>
<item name="menu_unlock_icon">@drawable/ic_unlocked_white_24dp</item>
<item name="menu_lock_icon">@drawable/ic_lock_white_24dp</item>

View File

@ -769,6 +769,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
CommunicationActions.startVoiceCall(getActivity(), recipient.get());
}
@Override
public void onSecureVideoClicked() {
CommunicationActions.startVideoCall(getActivity(), recipient.get());
}
@Override
public void onInSecureCallClicked() {
try {

View File

@ -49,6 +49,7 @@ import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.webrtc.SurfaceViewRenderer;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.SignalProtocolAddress;
@ -65,7 +66,10 @@ public class WebRtcCallActivity extends Activity {
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
private WebRtcCallScreen callScreen;
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
private WebRtcCallScreen callScreen;
private boolean enableVideoIfAvailable;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -82,6 +86,9 @@ public class WebRtcCallActivity extends Activity {
initializeResources();
processIntent(getIntent());
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
}
@ -173,10 +180,10 @@ public class WebRtcCallActivity extends Activity {
.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, event.getRecipient().toShortString(this)),
R.drawable.ic_mic_solid_24, R.drawable.ic_videocam_white_48dp)
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.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_answering));
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_answering), event.getLocalRenderer());
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_ANSWER_CALL);
@ -195,7 +202,7 @@ public class WebRtcCallActivity extends Activity {
intent.setAction(WebRtcCallService.ACTION_DENY_CALL);
startService(intent);
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ending_call));
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ending_call), event.getLocalRenderer());
delayedFinish();
}
}
@ -212,24 +219,24 @@ public class WebRtcCallActivity extends Activity {
}
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_dialing));
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_dialing), event.getLocalRenderer());
}
private void handleTerminate(@NonNull Recipient recipient /*, int terminationType */) {
private void handleTerminate(@NonNull Recipient recipient, @NonNull SurfaceViewRenderer localRenderer /*, int terminationType */) {
Log.i(TAG, "handleTerminate called");
callScreen.setActiveCall(recipient, getString(R.string.RedPhone_ending_call));
callScreen.setActiveCall(recipient, getString(R.string.RedPhone_ending_call), localRenderer);
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
delayedFinish();
}
private void handleCallRinging(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ringing));
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ringing), event.getLocalRenderer());
}
private void handleCallBusy(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_busy));
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_busy), event.getLocalRenderer());
delayedFinish(BUSY_SIGNAL_DELAY_FINISH);
}
@ -240,12 +247,12 @@ public class WebRtcCallActivity extends Activity {
}
private void handleRecipientUnavailable(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_recipient_unavailable));
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_recipient_unavailable), event.getLocalRenderer());
delayedFinish();
}
private void handleServerFailure(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_network_failed));
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_network_failed), event.getLocalRenderer());
delayedFinish();
}
@ -259,13 +266,13 @@ public class WebRtcCallActivity extends Activity {
dialog.setPositiveButton(R.string.RedPhone_got_it, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
WebRtcCallActivity.this.handleTerminate(event.getRecipient());
WebRtcCallActivity.this.handleTerminate(event.getRecipient(), event.getLocalRenderer());
}
});
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
WebRtcCallActivity.this.handleTerminate(event.getRecipient());
WebRtcCallActivity.this.handleTerminate(event.getRecipient(), event.getLocalRenderer());
}
});
dialog.show();
@ -294,7 +301,7 @@ public class WebRtcCallActivity extends Activity {
callScreen.setCancelIdentityButton(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleTerminate(recipient);
handleTerminate(recipient, event.getLocalRenderer());
}
});
}
@ -316,22 +323,27 @@ public class WebRtcCallActivity extends Activity {
Log.i(TAG, "Got message from service: " + event);
switch (event.getState()) {
case CALL_CONNECTED: handleCallConnected(event); break;
case NETWORK_FAILURE: handleServerFailure(event); break;
case CALL_RINGING: handleCallRinging(event); break;
case CALL_DISCONNECTED: handleTerminate(event.getRecipient()); break;
case NO_SUCH_USER: handleNoSuchUser(event); break;
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(event); break;
case CALL_INCOMING: handleIncomingCall(event); break;
case CALL_OUTGOING: handleOutgoingCall(event); break;
case CALL_BUSY: handleCallBusy(event); break;
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
case CALL_CONNECTED: handleCallConnected(event); break;
case NETWORK_FAILURE: handleServerFailure(event); break;
case CALL_RINGING: handleCallRinging(event); break;
case CALL_DISCONNECTED: handleTerminate(event.getRecipient(), event.getLocalRenderer()); break;
case NO_SUCH_USER: handleNoSuchUser(event); break;
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(event); break;
case CALL_INCOMING: handleIncomingCall(event); break;
case CALL_OUTGOING: handleOutgoingCall(event); break;
case CALL_BUSY: handleCallBusy(event); break;
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
}
callScreen.setRemoteVideoEnabled(event.isRemoteVideoEnabled());
callScreen.updateAudioState(event.isBluetoothAvailable(), event.isMicrophoneEnabled());
callScreen.setControlsEnabled(event.getState() != WebRtcViewModel.State.CALL_INCOMING);
callScreen.setLocalVideoState(event.getLocalCameraState());
callScreen.setLocalVideoState(event.getLocalCameraState(), event.getLocalRenderer());
if (event.getLocalCameraState().getCameraCount() > 0 && enableVideoIfAvailable) {
enableVideoIfAvailable = false;
handleSetMuteVideo(false);
}
}
private class HangupButtonListener implements WebRtcCallScreen.HangupButtonListener {

View File

@ -40,6 +40,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -67,6 +68,7 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
private SurfaceViewRenderer localRenderer;
private PercentFrameLayout localRenderLayout;
private PercentFrameLayout remoteRenderLayout;
private PercentFrameLayout localLargeRenderLayout;
private TextView name;
private TextView phoneNumber;
private TextView label;
@ -83,9 +85,8 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
private WebRtcAnswerDeclineButton incomingCallButton;
private LiveRecipient recipient;
private boolean minimized;
private LiveRecipient recipient;
private boolean minimized;
public WebRtcCallScreen(Context context) {
super(context);
@ -110,8 +111,9 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
endCallButton.show();
}
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message) {
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @NonNull SurfaceViewRenderer localRenderer) {
setCard(personInfo, message);
setRinging(localRenderer);
incomingCallButton.stopRingingAnimation();
incomingCallButton.setVisibility(View.GONE);
endCallButton.show();
@ -194,22 +196,22 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
this.controls.setControlsEnabled(enabled);
}
public void setLocalVideoState(@NonNull CameraState cameraState) {
public void setLocalVideoState(@NonNull CameraState cameraState, @NonNull SurfaceViewRenderer localRenderer) {
this.controls.setVideoAvailable(cameraState.getCameraCount() > 0);
this.controls.setVideoEnabled(cameraState.isEnabled());
this.controls.setCameraFlipAvailable(cameraState.getCameraCount() > 1);
this.controls.setCameraFlipClickable(cameraState.getActiveDirection() != CameraState.Direction.PENDING);
this.controls.setCameraFlipButtonEnabled(cameraState.getActiveDirection() == CameraState.Direction.BACK);
if (this.localRenderer != null) {
this.localRenderer.setMirror(cameraState.getActiveDirection() == CameraState.Direction.FRONT);
localRenderer.setMirror(cameraState.getActiveDirection() == CameraState.Direction.FRONT);
if (localRenderLayout.getChildCount() != 0) {
displayLocalRendererInSmallLayout(!cameraState.isEnabled());
} else {
displayLocalRendererInLargeLayout(!cameraState.isEnabled());
}
if (this.localRenderLayout.isHidden() == cameraState.isEnabled()) {
this.localRenderLayout.setHidden(!cameraState.isEnabled());
this.localRenderLayout.requestLayout();
this.localRenderer.setVisibility(cameraState.isEnabled() ? VISIBLE : INVISIBLE);
}
localRenderer.setVisibility(cameraState.isEnabled() ? VISIBLE : INVISIBLE);
}
public void setRemoteVideoEnabled(boolean enabled) {
@ -233,6 +235,42 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
return controls.isVideoEnabled();
}
private void displayLocalRendererInLargeLayout(boolean hide) {
if (localLargeRenderLayout.getChildCount() == 0) {
localRenderLayout.removeAllViews();
localLargeRenderLayout.addView(localRenderer);
}
localRenderLayout.setHidden(true);
localRenderLayout.requestLayout();
localLargeRenderLayout.setHidden(hide);
localLargeRenderLayout.requestLayout();
if (hide) {
photo.setVisibility(View.VISIBLE);
} else {
photo.setVisibility(View.INVISIBLE);
}
}
private void displayLocalRendererInSmallLayout(boolean hide) {
if (localRenderLayout.getChildCount() == 0) {
localLargeRenderLayout.removeAllViews();
localRenderLayout.addView(localRenderer);
}
localLargeRenderLayout.setHidden(true);
localLargeRenderLayout.requestLayout();
localRenderLayout.setHidden(hide);
localRenderLayout.requestLayout();
if (remoteRenderLayout.isHidden()) {
photo.setVisibility(View.VISIBLE);
}
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.webrtc_call_screen, this, true);
@ -241,6 +279,7 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
this.photo = findViewById(R.id.photo);
this.localRenderLayout = findViewById(R.id.local_render_layout);
this.remoteRenderLayout = findViewById(R.id.remote_render_layout);
this.localLargeRenderLayout = findViewById(R.id.local_large_render_layout);
this.phoneNumber = findViewById(R.id.phoneNumber);
this.name = findViewById(R.id.name);
this.label = findViewById(R.id.label);
@ -266,10 +305,28 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientForeverObs
});
}
private void setConnected(SurfaceViewRenderer localRenderer,
SurfaceViewRenderer remoteRenderer)
{
if (localRenderLayout.getChildCount() == 0 && remoteRenderLayout.getChildCount() == 0) {
private void setRinging(SurfaceViewRenderer localRenderer) {
if (localLargeRenderLayout.getChildCount() == 0) {
if (localRenderer.getParent() != null) {
((ViewGroup)localRenderer.getParent()).removeView(localRenderer);
}
localLargeRenderLayout.setPosition(0, 0, 100, 100);
localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
localRenderer.setMirror(true);
localRenderer.setZOrderMediaOverlay(true);
localLargeRenderLayout.addView(localRenderer);
this.localRenderer = localRenderer;
}
}
private void setConnected(SurfaceViewRenderer localRenderer, SurfaceViewRenderer remoteRenderer) {
if (localRenderLayout.getChildCount() == 0) {
if (localRenderer.getParent() != null) {
((ViewGroup)localRenderer.getParent()).removeView(localRenderer);
}

View File

@ -142,7 +142,7 @@ class ContactFieldAdapter extends RecyclerView.Adapter<ContactFieldAdapter.Conta
Field(@NonNull Context context, @NonNull Phone phoneNumber, @NonNull Locale locale) {
this.value = ContactUtil.getPrettyPhoneNumber(phoneNumber, locale);
this.iconResId = R.drawable.ic_call_white_24dp;
this.iconResId = R.drawable.ic_phone_right_unlock_solid_24;
this.iconUri = null;
this.maxLines = 1;
this.selectable = phoneNumber;

View File

@ -683,15 +683,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
if (isSecureText) {
if (recipient.get().getExpireMessages() > 0) {
inflater.inflate(R.menu.conversation_expiring_on, menu);
final MenuItem item = menu.findItem(R.id.menu_expiring_messages);
final View actionView = MenuItemCompat.getActionView(item);
final TextView badgeView = actionView.findViewById(R.id.expiration_badge);
badgeView.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(this, recipient.get().getExpireMessages()));
actionView.setOnClickListener(v -> onOptionsItemSelected(item));
titleView.showExpiring(recipient);
} else {
inflater.inflate(R.menu.conversation_expiring_off, menu);
titleView.clearExpiring();
}
}
@ -729,8 +724,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
if (recipient != null && recipient.get().isLocalNumber()) {
if (isSecureText) menu.findItem(R.id.menu_call_secure).setVisible(false);
else menu.findItem(R.id.menu_call_insecure).setVisible(false);
if (isSecureText) {
menu.findItem(R.id.menu_call_secure).setVisible(false);
menu.findItem(R.id.menu_video_secure).setVisible(false);
} else {
menu.findItem(R.id.menu_call_insecure).setVisible(false);
}
MenuItem muteItem = menu.findItem(R.id.menu_mute_notifications);
@ -798,6 +797,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_call_secure: handleDial(getRecipient(), true); return true;
case R.id.menu_video_secure: handleVideo(getRecipient()); return true;
case R.id.menu_call_insecure: handleDial(getRecipient(), false); return true;
case R.id.menu_view_media: handleViewMedia(); return true;
case R.id.menu_add_shortcut: handleAddShortcut(); return true;
@ -1135,6 +1135,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
}
private void handleVideo(final Recipient recipient) {
if (recipient == null) return;
CommunicationActions.startVideoCall(this, recipient);
}
private void handleDisplayGroupRecipients() {
new GroupMembersDialog(this, getRecipient()).display();
}

View File

@ -18,7 +18,9 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@ -38,6 +40,8 @@ public class ConversationTitleView extends RelativeLayout {
private ImageView verified;
private View subtitleContainer;
private View verifiedSubtitle;
private View expirationBadgeContainer;
private TextView expirationBadgeTime;
public ConversationTitleView(Context context) {
this(context, null);
@ -45,25 +49,37 @@ public class ConversationTitleView extends RelativeLayout {
public ConversationTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
this.content = ViewUtil.findById(this, R.id.content);
this.title = ViewUtil.findById(this, R.id.title);
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
this.verified = ViewUtil.findById(this, R.id.verified_indicator);
this.subtitleContainer = ViewUtil.findById(this, R.id.subtitle_container);
this.verifiedSubtitle = ViewUtil.findById(this, R.id.verified_subtitle);
this.avatar = ViewUtil.findById(this, R.id.contact_photo_image);
this.content = ViewUtil.findById(this, R.id.content);
this.title = ViewUtil.findById(this, R.id.title);
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
this.verified = ViewUtil.findById(this, R.id.verified_indicator);
this.subtitleContainer = ViewUtil.findById(this, R.id.subtitle_container);
this.verifiedSubtitle = ViewUtil.findById(this, R.id.verified_subtitle);
this.avatar = ViewUtil.findById(this, R.id.contact_photo_image);
this.expirationBadgeContainer = ViewUtil.findById(this, R.id.expiration_badge_container);
this.expirationBadgeTime = ViewUtil.findById(this, R.id.expiration_badge);
ViewUtil.setTextViewGravityStart(this.title, getContext());
ViewUtil.setTextViewGravityStart(this.subtitle, getContext());
}
public void showExpiring(@NonNull LiveRecipient recipient) {
expirationBadgeTime.setText(ExpirationUtil.getExpirationAbbreviatedDisplayValue(getContext(), recipient.get().getExpireMessages()));
expirationBadgeContainer.setVisibility(View.VISIBLE);
updateSubtitleVisibility();
}
public void clearExpiring() {
expirationBadgeContainer.setVisibility(View.GONE);
updateSubtitleVisibility();
}
public void setTitle(@NonNull GlideRequests glideRequests, @Nullable Recipient recipient) {
this.subtitleContainer.setVisibility(View.VISIBLE);
@ -106,7 +122,7 @@ public class ConversationTitleView extends RelativeLayout {
private void setComposeTitle() {
this.title.setText(R.string.ConversationActivity_compose_message);
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
updateSubtitleVisibility();
}
private void setRecipientTitle(Recipient recipient) {
@ -128,11 +144,11 @@ public class ConversationTitleView extends RelativeLayout {
if (TextUtils.isEmpty(recipient.getProfileName())) {
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
} else {
this.subtitle.setText("~" + recipient.getProfileName());
this.subtitle.setVisibility(View.VISIBLE);
}
updateSubtitleVisibility();
}
private void setContactRecipientTitle(Recipient recipient) {
@ -140,11 +156,11 @@ public class ConversationTitleView extends RelativeLayout {
if (TextUtils.isEmpty(recipient.getCustomLabel())) {
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
} else {
this.subtitle.setText(recipient.getCustomLabel());
this.subtitle.setVisibility(View.VISIBLE);
}
updateSubtitleVisibility();
}
private void setGroupRecipientTitle(Recipient recipient) {
@ -161,7 +177,7 @@ public class ConversationTitleView extends RelativeLayout {
.map(r -> r.toShortString(getContext()))
.collect(Collectors.joining(", ")));
this.subtitle.setVisibility(View.VISIBLE);
updateSubtitleVisibility();
}
private void setSelfTitle() {
@ -173,10 +189,15 @@ public class ConversationTitleView extends RelativeLayout {
final String displayName = recipient.getDisplayName(getContext());
this.title.setText(displayName);
this.subtitle.setText(null);
this.subtitle.setVisibility(View.GONE);
updateVerifiedSubtitleVisibility();
}
private void updateVerifiedSubtitleVisibility() {
verifiedSubtitle.setVisibility(subtitle.getVisibility() != VISIBLE && verified.getVisibility() == VISIBLE ? VISIBLE : GONE);
}
private void updateSubtitleVisibility() {
subtitle.setVisibility(expirationBadgeContainer.getVisibility() != VISIBLE && !TextUtils.isEmpty(subtitle.getText()) ? VISIBLE : GONE);
updateVerifiedSubtitleVisibility();
}
}

View File

@ -16,6 +16,7 @@ public class ContactPreference extends Preference {
private ImageView messageButton;
private ImageView callButton;
private ImageView secureCallButton;
private ImageView secureVideoButton;
private Listener listener;
private boolean secure;
@ -48,9 +49,10 @@ public class ContactPreference extends Preference {
public void onBindViewHolder(PreferenceViewHolder view) {
super.onBindViewHolder(view);
this.messageButton = (ImageView) view.findViewById(R.id.message);
this.callButton = (ImageView) view.findViewById(R.id.call);
this.secureCallButton = (ImageView) view.findViewById(R.id.secure_call);
this.messageButton = (ImageView) view.findViewById(R.id.message);
this.callButton = (ImageView) view.findViewById(R.id.call);
this.secureCallButton = (ImageView) view.findViewById(R.id.secure_call);
this.secureVideoButton = (ImageView) view.findViewById(R.id.secure_video);
if (listener != null) setListener(listener);
setSecure(secure);
@ -59,8 +61,9 @@ public class ContactPreference extends Preference {
public void setSecure(boolean secure) {
this.secure = secure;
if (secureCallButton != null) secureCallButton.setVisibility(secure ? View.VISIBLE : View.GONE);
if (callButton != null) callButton.setVisibility(secure ? View.GONE : View.VISIBLE);
if (secureCallButton != null) secureCallButton.setVisibility(secure ? View.VISIBLE : View.GONE);
if (secureVideoButton != null) secureVideoButton.setVisibility(secure ? View.VISIBLE : View.GONE);
if (callButton != null) callButton.setVisibility(secure ? View.GONE : View.VISIBLE);
int color;
@ -70,22 +73,25 @@ public class ContactPreference extends Preference {
color = getContext().getResources().getColor(R.color.grey_600);
}
if (messageButton != null) messageButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (secureCallButton != null) secureCallButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (callButton != null) callButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (messageButton != null) messageButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (secureCallButton != null) secureCallButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (secureVideoButton != null) secureVideoButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (callButton != null) callButton.setColorFilter(color, PorterDuff.Mode.SRC_IN);
}
public void setListener(Listener listener) {
this.listener = listener;
if (this.messageButton != null) this.messageButton.setOnClickListener(v -> listener.onMessageClicked());
if (this.secureCallButton != null) this.secureCallButton.setOnClickListener(v -> listener.onSecureCallClicked());
if (this.callButton != null) this.callButton.setOnClickListener(v -> listener.onInSecureCallClicked());
if (this.messageButton != null) this.messageButton.setOnClickListener(v -> listener.onMessageClicked());
if (this.secureCallButton != null) this.secureCallButton.setOnClickListener(v -> listener.onSecureCallClicked());
if (this.secureVideoButton != null) this.secureVideoButton.setOnClickListener(v -> listener.onSecureVideoClicked());
if (this.callButton != null) this.callButton.setOnClickListener(v -> listener.onInSecureCallClicked());
}
public interface Listener {
public void onMessageClicked();
public void onSecureCallClicked();
public void onSecureVideoClicked();
public void onInSecureCallClicked();
}

View File

@ -9,6 +9,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.TaskStackBuilder;
import android.text.TextUtils;
import android.widget.Toast;
@ -27,29 +28,34 @@ public class CommunicationActions {
if (TelephonyUtil.isAnyPstnLineBusy(activity)) {
Toast.makeText(activity,
R.string.CommunicationActions_a_cellular_call_is_already_in_progress,
Toast.LENGTH_SHORT
).show();
Toast.LENGTH_SHORT)
.show();
return;
}
Permissions.with(activity)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(activity.getString(R.string.ConversationActivity_to_call_s_signal_needs_access_to_your_microphone_and_camera, recipient.toShortString(activity)),
R.drawable.ic_mic_solid_24,
R.drawable.ic_videocam_white_48dp)
.withPermanentDenialDialog(activity.getString(R.string.ConversationActivity_signal_needs_the_microphone_and_camera_permissions_in_order_to_call_s, recipient.toShortString(activity)))
.onAllGranted(() -> {
Intent intent = new Intent(activity, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId());
activity.startService(intent);
new AlertDialog.Builder(activity)
.setMessage(R.string.CommunicationActions_start_voice_call)
.setPositiveButton(R.string.CommunicationActions_call, (d, w) -> startCallInternal(activity, recipient, false))
.setNegativeButton(R.string.CommunicationActions_cancel, (d, w) -> d.dismiss())
.setCancelable(true)
.show();
}
Intent activityIntent = new Intent(activity, WebRtcCallActivity.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(activityIntent);
})
.execute();
public static void startVideoCall(@NonNull Activity activity, @NonNull Recipient recipient) {
if (TelephonyUtil.isAnyPstnLineBusy(activity)) {
Toast.makeText(activity,
R.string.CommunicationActions_a_cellular_call_is_already_in_progress,
Toast.LENGTH_SHORT)
.show();
return;
}
new AlertDialog.Builder(activity)
.setMessage(R.string.CommunicationActions_start_video_call)
.setPositiveButton(R.string.CommunicationActions_call, (d, w) -> startCallInternal(activity, recipient, true))
.setNegativeButton(R.string.CommunicationActions_cancel, (d, w) -> d.dismiss())
.setCancelable(true)
.show();
}
public static void startConversation(@NonNull Context context, @NonNull Recipient recipient, @Nullable String text) {
@ -103,4 +109,31 @@ public class CommunicationActions {
Toast.makeText(context, R.string.CommunicationActions_no_browser_found, Toast.LENGTH_SHORT).show();
}
}
private static void startCallInternal(@NonNull Activity activity, @NonNull Recipient recipient, boolean isVideo) {
Permissions.with(activity)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(activity.getString(R.string.ConversationActivity_to_call_s_signal_needs_access_to_your_microphone_and_camera, recipient.getDisplayName(activity)),
R.drawable.ic_mic_solid_24,
R.drawable.ic_video_solid_24_tinted)
.withPermanentDenialDialog(activity.getString(R.string.ConversationActivity_signal_needs_the_microphone_and_camera_permissions_in_order_to_call_s, recipient.getDisplayName(activity)))
.onAllGranted(() -> {
Intent intent = new Intent(activity, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_RECIPIENT, recipient.getId());
activity.startService(intent);
Intent activityIntent = new Intent(activity, WebRtcCallActivity.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (isVideo) {
activityIntent.putExtra(WebRtcCallActivity.EXTRA_ENABLE_VIDEO_IF_AVAILABLE, true);
}
activity.startActivity(activityIntent);
})
.execute();
}
}