diff --git a/res/drawable-hdpi/ic_videocam_white_24dp.webp b/res/drawable-hdpi/ic_videocam_white_24dp.webp deleted file mode 100644 index daacaeb526..0000000000 Binary files a/res/drawable-hdpi/ic_videocam_white_24dp.webp and /dev/null differ diff --git a/res/drawable-hdpi/ic_videocam_white_48dp.webp b/res/drawable-hdpi/ic_videocam_white_48dp.webp deleted file mode 100644 index 08afed92e0..0000000000 Binary files a/res/drawable-hdpi/ic_videocam_white_48dp.webp and /dev/null differ diff --git a/res/drawable-mdpi/ic_videocam_white_24dp.webp b/res/drawable-mdpi/ic_videocam_white_24dp.webp deleted file mode 100644 index 9dc5fa6d04..0000000000 Binary files a/res/drawable-mdpi/ic_videocam_white_24dp.webp and /dev/null differ diff --git a/res/drawable-mdpi/ic_videocam_white_48dp.webp b/res/drawable-mdpi/ic_videocam_white_48dp.webp deleted file mode 100644 index c5e4f5bab1..0000000000 Binary files a/res/drawable-mdpi/ic_videocam_white_48dp.webp and /dev/null differ diff --git a/res/drawable-xhdpi/ic_videocam_white_24dp.webp b/res/drawable-xhdpi/ic_videocam_white_24dp.webp deleted file mode 100644 index c5e4f5bab1..0000000000 Binary files a/res/drawable-xhdpi/ic_videocam_white_24dp.webp and /dev/null differ diff --git a/res/drawable-xhdpi/ic_videocam_white_48dp.webp b/res/drawable-xhdpi/ic_videocam_white_48dp.webp deleted file mode 100644 index 0ac5fc5798..0000000000 Binary files a/res/drawable-xhdpi/ic_videocam_white_48dp.webp and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_call_white_24dp.webp b/res/drawable-xxhdpi/ic_call_white_24dp.webp deleted file mode 100644 index 41dc409e31..0000000000 Binary files a/res/drawable-xxhdpi/ic_call_white_24dp.webp and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_videocam_white_24dp.webp b/res/drawable-xxhdpi/ic_videocam_white_24dp.webp deleted file mode 100644 index 08afed92e0..0000000000 Binary files a/res/drawable-xxhdpi/ic_videocam_white_24dp.webp and /dev/null differ diff --git a/res/drawable-xxhdpi/ic_videocam_white_48dp.webp b/res/drawable-xxhdpi/ic_videocam_white_48dp.webp deleted file mode 100644 index 8ccb2d0de9..0000000000 Binary files a/res/drawable-xxhdpi/ic_videocam_white_48dp.webp and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_videocam_white_24dp.webp b/res/drawable-xxxhdpi/ic_videocam_white_24dp.webp deleted file mode 100644 index 0ac5fc5798..0000000000 Binary files a/res/drawable-xxxhdpi/ic_videocam_white_24dp.webp and /dev/null differ diff --git a/res/drawable-xxxhdpi/ic_videocam_white_48dp.webp b/res/drawable-xxxhdpi/ic_videocam_white_48dp.webp deleted file mode 100644 index 05c37229ea..0000000000 Binary files a/res/drawable-xxxhdpi/ic_videocam_white_48dp.webp and /dev/null differ diff --git a/res/drawable/ic_phone_right_solid_24.xml b/res/drawable/ic_phone_right_solid_24.xml new file mode 100644 index 0000000000..a523efa007 --- /dev/null +++ b/res/drawable/ic_phone_right_solid_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/drawable/ic_phone_right_unlock_solid_24.xml b/res/drawable/ic_phone_right_unlock_solid_24.xml new file mode 100644 index 0000000000..e3321a7147 --- /dev/null +++ b/res/drawable/ic_phone_right_unlock_solid_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/drawable/ic_video_solid_24.xml b/res/drawable/ic_video_solid_24.xml new file mode 100644 index 0000000000..7a2fa3ec99 --- /dev/null +++ b/res/drawable/ic_video_solid_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/drawable/ic_video_solid_24_tinted.xml b/res/drawable/ic_video_solid_24_tinted.xml new file mode 100644 index 0000000000..8d2b24b0f1 --- /dev/null +++ b/res/drawable/ic_video_solid_24_tinted.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/drawable/webrtc_video_mute_button.xml b/res/drawable/webrtc_video_mute_button.xml index 5cc40e123a..51a8733ed2 100644 --- a/res/drawable/webrtc_video_mute_button.xml +++ b/res/drawable/webrtc_video_mute_button.xml @@ -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"/> \ No newline at end of file diff --git a/res/layout/conversation_title_view.xml b/res/layout/conversation_title_view.xml index ffa7673f8e..a5f9098e66 100644 --- a/res/layout/conversation_title_view.xml +++ b/res/layout/conversation_title_view.xml @@ -53,14 +53,15 @@ android:layout_height="wrap_content" android:orientation="horizontal"> + + @@ -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" /> diff --git a/res/layout/expiration_timer_badge.xml b/res/layout/expiration_timer_badge.xml new file mode 100644 index 0000000000..aa023058f9 --- /dev/null +++ b/res/layout/expiration_timer_badge.xml @@ -0,0 +1,34 @@ + + + + + + + \ No newline at end of file diff --git a/res/layout/expiration_timer_menu.xml b/res/layout/expiration_timer_menu.xml deleted file mode 100644 index a4c52797e9..0000000000 --- a/res/layout/expiration_timer_menu.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/layout/item_selectable_contact_field.xml b/res/layout/item_selectable_contact_field.xml index 8074933c2a..7161a100c2 100644 --- a/res/layout/item_selectable_contact_field.xml +++ b/res/layout/item_selectable_contact_field.xml @@ -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" /> - + - + - + + + \ No newline at end of file diff --git a/res/layout/webrtc_call_screen.xml b/res/layout/webrtc_call_screen.xml index bbefd15a34..3d0247e814 100644 --- a/res/layout/webrtc_call_screen.xml +++ b/res/layout/webrtc_call_screen.xml @@ -26,6 +26,12 @@ android:layout_height="match_parent" tools:visibility="invisible" /> + + + + \ No newline at end of file diff --git a/res/menu/conversation_expiring_on.xml b/res/menu/conversation_expiring_on.xml index 5dbf88b001..cb9cba3269 100644 --- a/res/menu/conversation_expiring_on.xml +++ b/res/menu/conversation_expiring_on.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + app:showAsAction="never" + android:title="@string/conversation_expiring_off__disappearing_messages"/> \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 22fdba048a..306e474964 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -120,6 +120,10 @@ No web browser found. A cellular call is already in progress. + Start video call? + Start voice call? + Cancel + Call 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. @@ -1453,6 +1457,7 @@ Signal call + Signal video call Message details diff --git a/res/values/themes.xml b/res/values/themes.xml index 33b72463b5..4e0e00af87 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -302,7 +302,7 @@ @drawable/ic_add_white_24dp @drawable/ic_group_solid_24 @drawable/ic_search_24 - @drawable/ic_call_white_24dp + @drawable/ic_phone_right_unlock_solid_24 @drawable/ic_launch_white_24dp @drawable/ic_unlocked_white_24dp @drawable/ic_lock_white_24dp @@ -532,7 +532,7 @@ @drawable/ic_add_white_24dp @drawable/ic_group_solid_24 @drawable/ic_search_24 - @drawable/ic_call_white_24dp + @drawable/ic_phone_right_unlock_solid_24 @drawable/ic_launch_white_24dp @drawable/ic_unlocked_white_24dp @drawable/ic_lock_white_24dp diff --git a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java index cd9907c143..5af3f19036 100644 --- a/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java +++ b/src/org/thoughtcrime/securesms/RecipientPreferenceActivity.java @@ -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 { diff --git a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java index 3d43b58029..81e37e6c66 100644 --- a/src/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/src/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -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 { diff --git a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java index 3906ce9b11..4f5631ced0 100644 --- a/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java +++ b/src/org/thoughtcrime/securesms/components/webrtc/WebRtcCallScreen.java @@ -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); } diff --git a/src/org/thoughtcrime/securesms/contactshare/ContactFieldAdapter.java b/src/org/thoughtcrime/securesms/contactshare/ContactFieldAdapter.java index 3b2680bbf7..8805f7bfd3 100644 --- a/src/org/thoughtcrime/securesms/contactshare/ContactFieldAdapter.java +++ b/src/org/thoughtcrime/securesms/contactshare/ContactFieldAdapter.java @@ -142,7 +142,7 @@ class ContactFieldAdapter extends RecyclerView.Adapter 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(); } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java b/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java index 3c80611e45..f406a7d65a 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationTitleView.java @@ -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(); + } } diff --git a/src/org/thoughtcrime/securesms/preferences/widgets/ContactPreference.java b/src/org/thoughtcrime/securesms/preferences/widgets/ContactPreference.java index b8707fd605..727e4ae5b0 100644 --- a/src/org/thoughtcrime/securesms/preferences/widgets/ContactPreference.java +++ b/src/org/thoughtcrime/securesms/preferences/widgets/ContactPreference.java @@ -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(); } diff --git a/src/org/thoughtcrime/securesms/util/CommunicationActions.java b/src/org/thoughtcrime/securesms/util/CommunicationActions.java index cd8237c386..53ef04b132 100644 --- a/src/org/thoughtcrime/securesms/util/CommunicationActions.java +++ b/src/org/thoughtcrime/securesms/util/CommunicationActions.java @@ -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(); + } }