Clean up camera flipping, handle having missing cameras.

Did a refactor to better organize the camera flipping code. Also, I
wanted to make sure we handle the cases where the user doesn't have two
cameras (or no cameras, for that matter). In these cases, we just don't
show the appropriate buttons.
master
Greyson Parrelli 2018-04-25 11:00:03 -07:00
parent f1c79eaebf
commit ca8fecea9c
11 changed files with 372 additions and 258 deletions

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="5dp"
android:left="5dp"
android:right="5dp"
android:bottom="5dp"
android:drawable="@drawable/quick_camera_front"/>
</layer-list>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/webrtc_control_background"/>
<item android:top="5dp"
android:left="5dp"
android:right="5dp"

View File

@ -41,6 +41,7 @@
android:id="@+id/camera_flip_button"
style="@style/WebRtcCallCompoundButton"
android:contentDescription="@string/redphone_call_controls__flip_camera_rear"
android:background="@drawable/webrtc_camera_flip_button"/>
android:background="@drawable/webrtc_camera_rear_button"
android:visibility="gone"/>
</merge>

View File

@ -995,7 +995,7 @@
<!--- redphone_call_controls -->
<string name="redphone_call_card__signal_call">Signal Call</string>
<string name="redphone_call_controls__mute">Mute</string>
<string name="redphone_call_controls__flip_camera_rear">Use rear camera</string>
<string name="redphone_call_controls__flip_camera_rear">Switch Cameras</string>
<string name="redphone_call_controls__signal_call">Signal Call</string>
<!-- registration_activity -->

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.thoughtcrime.securesms.webrtc.CameraState;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.SignalProtocolAddress;
@ -160,10 +161,9 @@ public class WebRtcCallActivity extends Activity {
startService(intent);
}
private void handleSetCameraFlip(boolean isRear) {
private void handleFlipCamera() {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_SET_CAMERA_FLIP);
intent.putExtra(WebRtcCallService.EXTRA_CAMERA_FLIP_REAR, isRear);
intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA);
startService(intent);
}
@ -330,10 +330,10 @@ public class WebRtcCallActivity extends Activity {
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
}
callScreen.setLocalVideoEnabled(event.isLocalVideoEnabled());
callScreen.setRemoteVideoEnabled(event.isRemoteVideoEnabled());
callScreen.updateAudioState(event.isBluetoothAvailable(), event.isMicrophoneEnabled());
callScreen.setControlsEnabled(event.getState() != WebRtcViewModel.State.CALL_INCOMING);
callScreen.setLocalVideoState(event.getLocalCameraState());
}
private class HangupButtonListener implements WebRtcCallScreen.HangupButtonListener {
@ -358,7 +358,9 @@ public class WebRtcCallActivity extends Activity {
private class CameraFlipButtonListener implements WebRtcCallControls.CameraFlipButtonListener {
@Override
public void onToggle(boolean isRear) { WebRtcCallActivity.this.handleSetCameraFlip(isRear); }
public void onToggle() {
WebRtcCallActivity.this.handleFlipCamera();
}
}
private class SpeakerButtonListener implements WebRtcCallControls.SpeakerButtonListener {

View File

@ -5,8 +5,8 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.webrtc.CameraState;
public class WebRtcCallControls extends LinearLayout {
@ -27,9 +28,10 @@ public class WebRtcCallControls extends LinearLayout {
private AccessibleToggleButton audioMuteButton;
private AccessibleToggleButton videoMuteButton;
private AccessibleToggleButton cameraFlipButton;
private AccessibleToggleButton speakerButton;
private AccessibleToggleButton bluetoothButton;
private AccessibleToggleButton cameraFlipButton;
private boolean cameraFlipAvailable;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WebRtcCallControls(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
@ -62,7 +64,6 @@ public class WebRtcCallControls extends LinearLayout {
this.audioMuteButton = ViewUtil.findById(this, R.id.muteButton);
this.videoMuteButton = ViewUtil.findById(this, R.id.video_mute_button);
this.cameraFlipButton = ViewUtil.findById(this, R.id.camera_flip_button);
this.cameraFlipButton.setVisibility(View.INVISIBLE); // shown once video is enabled
}
public void setAudioMuteButtonListener(final MuteButtonListener listener) {
@ -80,7 +81,7 @@ public class WebRtcCallControls extends LinearLayout {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
boolean videoMuted = !isChecked;
listener.onToggle(videoMuted);
cameraFlipButton.setVisibility(videoMuted ? View.INVISIBLE : View.VISIBLE);
cameraFlipButton.setVisibility(!videoMuted && cameraFlipAvailable ? View.VISIBLE : View.GONE);
}
});
}
@ -89,7 +90,10 @@ public class WebRtcCallControls extends LinearLayout {
cameraFlipButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listener.onToggle(isChecked);
listener.onToggle();
cameraFlipButton.setBackgroundResource(isChecked ? R.drawable.webrtc_camera_front_button
: R.drawable.webrtc_camera_rear_button);
cameraFlipButton.setEnabled(false);
}
});
}
@ -143,40 +147,46 @@ public class WebRtcCallControls extends LinearLayout {
videoMuteButton.setChecked(enabled, false);
}
public void setVideoAvailable(boolean available) {
videoMuteButton.setVisibility(available ? VISIBLE : GONE);
}
public void setCameraFlipButtonEnabled(boolean enabled) {
cameraFlipButton.setChecked(enabled, false);
}
public void setCameraFlipAvailable(boolean available) {
cameraFlipAvailable = available;
}
public void setCameraFlipClickable(boolean clickable) {
setControlEnabled(cameraFlipButton, clickable);
}
public void setMicrophoneEnabled(boolean enabled) {
audioMuteButton.setChecked(!enabled, false);
}
public void setControlsEnabled(boolean enabled) {
if (enabled && Build.VERSION.SDK_INT >= 11) {
speakerButton.setAlpha(1.0f);
bluetoothButton.setAlpha(1.0f);
videoMuteButton.setAlpha(1.0f);
cameraFlipButton.setAlpha(1.0f);
audioMuteButton.setAlpha(1.0f);
setControlEnabled(speakerButton, enabled);
setControlEnabled(bluetoothButton, enabled);
setControlEnabled(videoMuteButton, enabled);
setControlEnabled(cameraFlipButton, enabled);
setControlEnabled(audioMuteButton, enabled);
}
speakerButton.setEnabled(true);
bluetoothButton.setEnabled(true);
videoMuteButton.setEnabled(true);
cameraFlipButton.setEnabled(true);
audioMuteButton.setEnabled(true);
} else if (!enabled && Build.VERSION.SDK_INT >= 11) {
speakerButton.setAlpha(0.3f);
bluetoothButton.setAlpha(0.3f);
videoMuteButton.setAlpha(0.3f);
cameraFlipButton.setAlpha(0.3f);
audioMuteButton.setAlpha(0.3f);
speakerButton.setEnabled(false);
bluetoothButton.setEnabled(false);
videoMuteButton.setEnabled(false);
cameraFlipButton.setEnabled(false);
audioMuteButton.setEnabled(false);
private void setControlEnabled(@NonNull View view, boolean enabled) {
if (enabled) {
view.setAlpha(1.0f);
view.setEnabled(true);
} else {
view.setAlpha(0.3f);
view.setEnabled(false);
}
}
public void displayVideoTooltip(ViewGroup viewGroup) {
if (Build.VERSION.SDK_INT > 15) {
if (Build.VERSION.SDK_INT > 15 && videoMuteButton.getVisibility() == VISIBLE) {
final ToolTipsManager toolTipsManager = new ToolTipsManager();
ToolTip toolTip = new ToolTip.Builder(getContext(), videoMuteButton, viewGroup,
@ -184,12 +194,7 @@ public class WebRtcCallControls extends LinearLayout {
ToolTip.POSITION_BELOW).build();
toolTipsManager.show(toolTip);
videoMuteButton.postDelayed(new Runnable() {
@Override
public void run() {
toolTipsManager.findAndDismiss(videoMuteButton);
}
}, 4000);
videoMuteButton.postDelayed(() -> toolTipsManager.findAndDismiss(videoMuteButton), 4000);
}
}
@ -198,7 +203,7 @@ public class WebRtcCallControls extends LinearLayout {
}
public static interface CameraFlipButtonListener {
public void onToggle(boolean isRear);
public void onToggle();
}
public static interface SpeakerButtonListener {

View File

@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VerifySpan;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.webrtc.SurfaceViewRenderer;
import org.whispersystems.libsignal.IdentityKey;
@ -62,6 +63,7 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi
private static final String TAG = WebRtcCallScreen.class.getSimpleName();
private ImageView photo;
private SurfaceViewRenderer localRenderer;
private PercentFrameLayout localRenderLayout;
private PercentFrameLayout remoteRenderLayout;
private TextView name;
@ -187,14 +189,19 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi
this.controls.setControlsEnabled(enabled);
}
public void setLocalVideoEnabled(boolean enabled) {
if (enabled && this.localRenderLayout.isHidden()) {
this.controls.setVideoEnabled(true);
this.localRenderLayout.setHidden(false);
this.localRenderLayout.requestLayout();
} else if (!enabled && !this.localRenderLayout.isHidden()){
this.controls.setVideoEnabled(false);
this.localRenderLayout.setHidden(true);
public void setLocalVideoState(@NonNull CameraState cameraState) {
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);
}
if (this.localRenderLayout.isHidden() == cameraState.isEnabled()) {
this.localRenderLayout.setHidden(!cameraState.isEnabled());
this.localRenderLayout.requestLayout();
}
}
@ -276,6 +283,8 @@ public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedLi
localRenderLayout.addView(localRenderer);
remoteRenderLayout.addView(remoteRenderer);
this.localRenderer = localRenderer;
}
}

View File

@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.whispersystems.libsignal.IdentityKey;
public class WebRtcViewModel {
@ -30,29 +31,40 @@ public class WebRtcViewModel {
private final @Nullable IdentityKey identityKey;
private final boolean remoteVideoEnabled;
private final boolean localVideoEnabled;
private final boolean isBluetoothAvailable;
private final boolean isMicrophoneEnabled;
public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient,
boolean localVideoEnabled, boolean remoteVideoEnabled,
boolean isBluetoothAvailable, boolean isMicrophoneEnabled)
private final CameraState localCameraState;
public WebRtcViewModel(@NonNull State state,
@NonNull Recipient recipient,
@NonNull CameraState localCameraState,
boolean remoteVideoEnabled,
boolean isBluetoothAvailable,
boolean isMicrophoneEnabled)
{
this(state, recipient, null,
localVideoEnabled, remoteVideoEnabled,
isBluetoothAvailable, isMicrophoneEnabled);
this(state,
recipient,
null,
localCameraState,
remoteVideoEnabled,
isBluetoothAvailable,
isMicrophoneEnabled);
}
public WebRtcViewModel(@NonNull State state, @NonNull Recipient recipient,
public WebRtcViewModel(@NonNull State state,
@NonNull Recipient recipient,
@Nullable IdentityKey identityKey,
boolean localVideoEnabled, boolean remoteVideoEnabled,
boolean isBluetoothAvailable, boolean isMicrophoneEnabled)
@NonNull CameraState localCameraState,
boolean remoteVideoEnabled,
boolean isBluetoothAvailable,
boolean isMicrophoneEnabled)
{
this.state = state;
this.recipient = recipient;
this.localCameraState = localCameraState;
this.identityKey = identityKey;
this.localVideoEnabled = localVideoEnabled;
this.remoteVideoEnabled = remoteVideoEnabled;
this.isBluetoothAvailable = isBluetoothAvailable;
this.isMicrophoneEnabled = isMicrophoneEnabled;
@ -66,8 +78,11 @@ public class WebRtcViewModel {
return recipient;
}
@Nullable
public IdentityKey getIdentityKey() {
public @NonNull CameraState getLocalCameraState() {
return localCameraState;
}
public @Nullable IdentityKey getIdentityKey() {
return identityKey;
}
@ -75,10 +90,6 @@ public class WebRtcViewModel {
return remoteVideoEnabled;
}
public boolean isLocalVideoEnabled() {
return localVideoEnabled;
}
public boolean isBluetoothAvailable() {
return isBluetoothAvailable;
}
@ -87,7 +98,8 @@ public class WebRtcViewModel {
return isMicrophoneEnabled;
}
public String toString() {
return "[State: " + state + ", recipient: " + recipient.getAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localVideoEnabled + "]";
return "[State: " + state + ", recipient: " + recipient.getAddress() + ", identity: " + identityKey + ", remoteVideo: " + remoteVideoEnabled + ", localVideo: " + localCameraState.isEnabled() + "]";
}
}

View File

@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
@ -43,6 +42,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver;
import org.thoughtcrime.securesms.webrtc.PeerConnectionFactoryOptions;
import org.thoughtcrime.securesms.webrtc.PeerConnectionWrapper;
@ -104,7 +104,12 @@ import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INC
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING;
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING;
public class WebRtcCallService extends Service implements InjectableType, PeerConnection.Observer, DataChannel.Observer, BluetoothStateManager.BluetoothStateListener {
public class WebRtcCallService extends Service implements InjectableType,
PeerConnection.Observer,
DataChannel.Observer,
BluetoothStateManager.BluetoothStateListener,
PeerConnectionWrapper.CameraEventListener
{
private static final String TAG = WebRtcCallService.class.getSimpleName();
@ -116,7 +121,6 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
public static final String EXTRA_REMOTE_ADDRESS = "remote_address";
public static final String EXTRA_MUTE = "mute_value";
public static final String EXTRA_CAMERA_FLIP_REAR = "camera_flip_rear_value";
public static final String EXTRA_AVAILABLE = "enabled_value";
public static final String EXTRA_REMOTE_DESCRIPTION = "remote_description";
public static final String EXTRA_TIMESTAMP = "timestamp";
@ -133,7 +137,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
public static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP";
public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO";
public static final String ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO";
public static final String ACTION_SET_CAMERA_FLIP = "SET_CAMERA_FLIP";
public static final String ACTION_FLIP_CAMERA = "FLIP_CAMERA";
public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE";
public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE";
public static final String ACTION_SCREEN_OFF = "SCREEN_OFF";
@ -149,11 +153,11 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
public static final String ACTION_REMOTE_VIDEO_MUTE = "REMOTE_VIDEO_MUTE";
public static final String ACTION_ICE_CONNECTED = "ICE_CONNECTED";
private CallState callState = CallState.STATE_IDLE;
private boolean microphoneEnabled = true;
private boolean localVideoEnabled = false;
private boolean remoteVideoEnabled = false;
private boolean bluetoothAvailable = false;
private CallState callState = CallState.STATE_IDLE;
private CameraState localCameraState = CameraState.UNKNOWN;
private boolean microphoneEnabled = true;
private boolean remoteVideoEnabled = false;
private boolean bluetoothAvailable = false;
@Inject public SignalServiceMessageSender messageSender;
@Inject public SignalServiceAccountManager accountManager;
@ -210,7 +214,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
else if (intent.getAction().equals(ACTION_REMOTE_HANGUP)) handleRemoteHangup(intent);
else if (intent.getAction().equals(ACTION_SET_MUTE_AUDIO)) handleSetMuteAudio(intent);
else if (intent.getAction().equals(ACTION_SET_MUTE_VIDEO)) handleSetMuteVideo(intent);
else if (intent.getAction().equals(ACTION_SET_CAMERA_FLIP)) handleSetCameraFlip(intent);
else if (intent.getAction().equals(ACTION_FLIP_CAMERA)) handleSetCameraFlip(intent);
else if (intent.getAction().equals(ACTION_BLUETOOTH_CHANGE)) handleBluetoothChange(intent);
else if (intent.getAction().equals(ACTION_WIRED_HEADSET_CHANGE)) handleWiredHeadsetChange(intent);
else if (intent.getAction().equals((ACTION_SCREEN_OFF))) handleScreenOffChange(intent);
@ -265,6 +269,15 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
startService(intent);
}
@Override
public void onCameraSwitchCompleted(@NonNull CameraState newCameraState) {
this.localCameraState = newCameraState;
if (recipient != null) {
sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
}
// Initializers
private void initializeResources() {
@ -358,7 +371,8 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this);
WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, !isSystemContact || isAlwaysTurn);
WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, WebRtcCallService.this, !isSystemContact || isAlwaysTurn);
WebRtcCallService.this.localCameraState = WebRtcCallService.this.peerConnection.getCameraState();
WebRtcCallService.this.peerConnection.setRemoteDescription(new SessionDescription(SessionDescription.Type.OFFER, offer));
WebRtcCallService.this.lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);
@ -379,6 +393,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
terminate();
}
});
if (recipient != null) {
sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
} catch (PeerConnectionException e) {
Log.w(TAG, e);
terminate();
@ -400,7 +418,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
initializeVideo();
sendMessage(WebRtcViewModel.State.CALL_OUTGOING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_OUTGOING, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
audioManager.initializeAudioForCall();
audioManager.startOutgoingRinger(OutgoingRinger.Type.SONAR);
@ -417,7 +435,8 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
try {
boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this);
WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, isAlwaysTurn);
WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, WebRtcCallService.this, isAlwaysTurn);
WebRtcCallService.this.localCameraState = WebRtcCallService.this.peerConnection.getCameraState();
WebRtcCallService.this.dataChannel = WebRtcCallService.this.peerConnection.createDataChannel(DATA_CHANNEL_NAME);
WebRtcCallService.this.dataChannel.registerObserver(WebRtcCallService.this);
@ -434,16 +453,20 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
Log.w(TAG, error);
if (error instanceof UntrustedIdentityException) {
sendMessage(WebRtcViewModel.State.UNTRUSTED_IDENTITY, recipient, ((UntrustedIdentityException)error).getIdentityKey(), localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.UNTRUSTED_IDENTITY, recipient, ((UntrustedIdentityException)error).getIdentityKey(), localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
} else if (error instanceof UnregisteredUserException) {
sendMessage(WebRtcViewModel.State.NO_SUCH_USER, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.NO_SUCH_USER, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
} else if (error instanceof IOException) {
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
terminate();
}
});
if (recipient != null) {
sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
} catch (PeerConnectionException e) {
Log.w(TAG, e);
terminate();
@ -475,7 +498,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
@Override
public void onFailureContinue(Throwable error) {
Log.w(TAG, error);
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
terminate();
}
@ -529,7 +552,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
@Override
public void onFailureContinue(Throwable error) {
Log.w(TAG, error);
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
terminate();
}
@ -543,7 +566,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
this.callState = CallState.STATE_LOCAL_RINGING;
this.lockManager.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
sendMessage(WebRtcViewModel.State.CALL_INCOMING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_INCOMING, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
startCallCardActivity();
audioManager.initializeAudioForCall();
@ -565,7 +588,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
this.callState = CallState.STATE_REMOTE_RINGING;
this.audioManager.startOutgoingRinger(OutgoingRinger.Type.RINGING);
sendMessage(WebRtcViewModel.State.CALL_RINGING, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_RINGING, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
}
@ -589,10 +612,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
callState = CallState.STATE_CONNECTED;
if (localVideoEnabled) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
if (localCameraState.isEnabled()) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
unregisterPowerButtonReceiver();
@ -600,12 +623,12 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
this.peerConnection.setCommunicationMode();
this.peerConnection.setAudioEnabled(microphoneEnabled);
this.peerConnection.setVideoEnabled(localVideoEnabled);
this.peerConnection.setVideoEnabled(localCameraState.isEnabled());
this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder()
.setVideoStreamingStatus(WebRtcDataProtos.VideoStreamingStatus.newBuilder()
.setId(this.callId)
.setEnabled(localVideoEnabled))
.setEnabled(localCameraState.isEnabled()))
.build().toByteArray()), false));
}
@ -644,7 +667,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
return;
}
sendMessage(WebRtcViewModel.State.CALL_BUSY, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_BUSY, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
audioManager.startOutgoingRinger(OutgoingRinger.Type.BUSY);
Util.runOnMainDelayed(new Runnable() {
@ -666,7 +689,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
this.callState != CallState.STATE_CONNECTED)
{
Log.w(TAG, "Timing out call: " + this.callId);
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
if (this.callState == CallState.STATE_ANSWERING || this.callState == CallState.STATE_LOCAL_RINGING) {
insertMissedCall(this.recipient, true);
@ -735,7 +758,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder().setHangup(Hangup.newBuilder().setId(this.callId)).build().toByteArray()), false));
sendMessage(this.recipient, SignalServiceCallMessage.forHangup(new HangupMessage(this.callId)));
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
terminate();
@ -752,9 +775,9 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
}
if (this.callState == CallState.STATE_DIALING || this.callState == CallState.STATE_REMOTE_RINGING) {
sendMessage(WebRtcViewModel.State.RECIPIENT_UNAVAILABLE, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.RECIPIENT_UNAVAILABLE, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
} else {
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
if (this.callState == CallState.STATE_ANSWERING || this.callState == CallState.STATE_LOCAL_RINGING) {
@ -777,26 +800,25 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
AudioManager audioManager = ServiceUtil.getAudioManager(this);
boolean muted = intent.getBooleanExtra(EXTRA_MUTE, false);
this.localVideoEnabled = !muted;
if (this.peerConnection != null) {
this.peerConnection.setVideoEnabled(this.localVideoEnabled);
this.peerConnection.setVideoEnabled(!muted);
this.localCameraState = this.peerConnection.getCameraState();
}
if (this.callId != null && this.dataChannel != null) {
this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder()
.setVideoStreamingStatus(WebRtcDataProtos.VideoStreamingStatus.newBuilder()
.setId(this.callId)
.setEnabled(localVideoEnabled))
.setEnabled(!muted))
.build().toByteArray()), false));
}
if (callState == CallState.STATE_CONNECTED) {
if (localVideoEnabled) this.lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
else this.lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
if (localCameraState.isEnabled()) this.lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
else this.lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL);
}
if (localVideoEnabled &&
if (localCameraState.isEnabled() &&
!audioManager.isSpeakerphoneOn() &&
!audioManager.isBluetoothScoOn() &&
!audioManager.isWiredHeadsetOn())
@ -804,16 +826,17 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
audioManager.setSpeakerphoneOn(true);
}
sendMessage(viewModelStateFor(callState), this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(viewModelStateFor(callState), this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
private void handleSetCameraFlip(Intent intent) {
boolean isRear = intent.getBooleanExtra(EXTRA_CAMERA_FLIP_REAR, false);
Log.w(TAG, "handleSetCameraFlip(isRear=" + isRear + ")...");
Log.w(TAG, "handleSetCameraFlip()...");
if (this.localVideoEnabled) {
if (this.peerConnection != null) {
this.peerConnection.flipCameras(isRear);
if (localCameraState.isEnabled() && peerConnection != null) {
peerConnection.flipCamera();
localCameraState = peerConnection.getCameraState();
if (recipient != null) {
sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
}
}
@ -822,7 +845,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
this.bluetoothAvailable = intent.getBooleanExtra(EXTRA_AVAILABLE, false);
if (recipient != null) {
sendMessage(viewModelStateFor(callState), recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
}
@ -839,12 +862,12 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
if (present && audioManager.isSpeakerphoneOn()) {
audioManager.setSpeakerphoneOn(false);
audioManager.setBluetoothScoOn(false);
} else if (!present && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && localVideoEnabled) {
} else if (!present && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && localCameraState.isEnabled()) {
audioManager.setSpeakerphoneOn(true);
}
if (recipient != null) {
sendMessage(viewModelStateFor(callState), recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(viewModelStateFor(callState), recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
}
}
@ -868,7 +891,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
}
this.remoteVideoEnabled = !muted;
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, this.recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
sendMessage(WebRtcViewModel.State.CALL_CONNECTED, this.recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled);
}
/// Helper Methods
@ -932,10 +955,10 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
}
this.callState = CallState.STATE_IDLE;
this.localCameraState = CameraState.UNKNOWN;
this.recipient = null;
this.callId = null;
this.microphoneEnabled = true;
this.localVideoEnabled = false;
this.remoteVideoEnabled = false;
this.pendingOutgoingIceUpdates = null;
this.pendingIncomingIceUpdates = null;
@ -944,20 +967,24 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
private void sendMessage(@NonNull WebRtcViewModel.State state,
@NonNull Recipient recipient,
boolean localVideoEnabled, boolean remoteVideoEnabled,
boolean bluetoothAvailable, boolean microphoneEnabled)
@NonNull Recipient recipient,
@NonNull CameraState localCameraState,
boolean remoteVideoEnabled,
boolean bluetoothAvailable,
boolean microphoneEnabled)
{
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled));
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled));
}
private void sendMessage(@NonNull WebRtcViewModel.State state,
@NonNull Recipient recipient,
@NonNull IdentityKey identityKey,
boolean localVideoEnabled, boolean remoteVideoEnabled,
boolean bluetoothAvailable, boolean microphoneEnabled)
@NonNull Recipient recipient,
@NonNull IdentityKey identityKey,
@NonNull CameraState localCameraState,
boolean remoteVideoEnabled,
boolean bluetoothAvailable,
boolean microphoneEnabled)
{
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, identityKey, localVideoEnabled, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled));
EventBus.getDefault().postSticky(new WebRtcViewModel(state, recipient, identityKey, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled));
}
private ListenableFutureTask<Boolean> sendMessage(@NonNull final Recipient recipient,

View File

@ -0,0 +1,32 @@
package org.thoughtcrime.securesms.webrtc;
import android.support.annotation.NonNull;
public class CameraState {
public static final CameraState UNKNOWN = new CameraState(Direction.NONE, 0);
private final Direction activeDirection;
private final int cameraCount;
public CameraState(@NonNull Direction activeDirection, int cameraCount) {
this.activeDirection = activeDirection;
this.cameraCount = cameraCount;
}
public int getCameraCount() {
return cameraCount;
}
public Direction getActiveDirection() {
return activeDirection;
}
public boolean isEnabled() {
return this.activeDirection != Direction.NONE;
}
public enum Direction {
FRONT, BACK, NONE, PENDING
}
}

View File

@ -21,7 +21,6 @@ import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoRenderer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
@ -30,6 +29,11 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.BACK;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.FRONT;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.NONE;
import static org.thoughtcrime.securesms.webrtc.CameraState.Direction.PENDING;
public class PeerConnectionWrapper {
private static final String TAG = PeerConnectionWrapper.class.getSimpleName();
@ -38,25 +42,17 @@ public class PeerConnectionWrapper {
@NonNull private final PeerConnection peerConnection;
@NonNull private final AudioTrack audioTrack;
@NonNull private final AudioSource audioSource;
@Nullable private final VideoCapturer videoCapturer;
@Nullable private final VideoCapturer videoCapturerRear;
@NonNull private final Camera camera;
@Nullable private final VideoSource videoSource;
@Nullable private final VideoSource videoSourceRear;
@Nullable private final VideoTrack videoTrack;
@Nullable private final VideoTrack videoTrackRear;
@Nullable private VideoCapturer videoCapturerActive;
@Nullable private VideoTrack videoTrackActive;
@Nullable private final MediaStream mediaStream;
public PeerConnectionWrapper(@NonNull Context context,
@NonNull PeerConnectionFactory factory,
@NonNull PeerConnection.Observer observer,
@NonNull VideoRenderer.Callbacks localRenderer,
public PeerConnectionWrapper(@NonNull Context context,
@NonNull PeerConnectionFactory factory,
@NonNull PeerConnection.Observer observer,
@NonNull VideoRenderer.Callbacks localRenderer,
@NonNull List<PeerConnection.IceServer> turnServers,
boolean hideIp)
@NonNull CameraEventListener cameraEventListener,
boolean hideIp)
{
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
iceServers.add(STUN_SERVER);
@ -80,69 +76,42 @@ public class PeerConnectionWrapper {
this.peerConnection.setAudioPlayout(false);
this.peerConnection.setAudioRecording(false);
this.videoCapturer = createVideoCapturer(context, false);
this.videoCapturerRear = createVideoCapturer(context, true);
this.videoCapturerActive = videoCapturer;
this.mediaStream = factory.createLocalMediaStream("ARDAMS");
MediaStream mediaStream = factory.createLocalMediaStream("ARDAMS");
this.audioSource = factory.createAudioSource(audioConstraints);
this.audioTrack = factory.createAudioTrack("ARDAMSa0", audioSource);
this.audioTrack.setEnabled(false);
this.mediaStream.addTrack(audioTrack);
mediaStream.addTrack(audioTrack);
if (videoCapturer != null) {
this.videoSource = factory.createVideoSource(videoCapturer);
this.camera = new Camera(context, cameraEventListener);
if (camera.capturer != null) {
this.videoSource = factory.createVideoSource(camera.capturer);
this.videoTrack = factory.createVideoTrack("ARDAMSv0", videoSource);
this.videoTrackActive = videoTrack;
this.videoTrack.addRenderer(new VideoRenderer(localRenderer));
this.videoTrack.setEnabled(false);
this.mediaStream.addTrack(videoTrack);
mediaStream.addTrack(videoTrack);
} else {
this.videoSource = null;
this.videoTrack = null;
this.videoTrackActive = null;
}
if (videoCapturerRear != null) {
this.videoSourceRear = factory.createVideoSource(videoCapturerRear);
this.videoTrackRear = factory.createVideoTrack("ARDAMSv0", videoSourceRear);
this.videoTrackRear.addRenderer(new VideoRenderer(localRenderer));
this.videoTrackRear.setEnabled(false);
} else {
this.videoSourceRear = null;
this.videoTrackRear = null;
}
this.peerConnection.addStream(mediaStream);
}
public void setVideoEnabled(boolean enabled) {
if (this.videoTrackActive != null) {
this.videoTrackActive.setEnabled(enabled);
}
if (this.videoCapturerActive != null) {
try {
if (enabled) this.videoCapturerActive.startCapture(1280, 720, 30);
else this.videoCapturerActive.stopCapture();
} catch (InterruptedException e) {
Log.w(TAG, e);
}
if (this.videoTrack != null) {
this.videoTrack.setEnabled(enabled);
}
camera.setEnabled(enabled);
}
public void flipCameras(boolean isRear) {
if (videoCapturerRear != null) {
setVideoEnabled(false);
mediaStream.removeTrack(videoTrackActive);
this.videoTrackActive = isRear ? videoTrackRear : videoTrack;
this.videoCapturerActive = isRear ? videoCapturerRear : videoCapturer;
mediaStream.addTrack(videoTrackActive);
setVideoEnabled(true);
}
public void flipCamera() {
camera.flip();
}
public CameraState getCameraState() {
return new CameraState(camera.getActiveDirection(), camera.getCount());
}
public void setCommunicationMode() {
@ -294,32 +263,12 @@ public class PeerConnectionWrapper {
}
public void dispose() {
if (this.videoCapturer != null) {
try {
this.videoCapturer.stopCapture();
} catch (InterruptedException e) {
Log.w(TAG, e);
}
this.videoCapturer.dispose();
}
if (this.videoCapturerRear != null) {
try {
this.videoCapturerRear.stopCapture();
} catch (InterruptedException e) {
Log.w(TAG, e);
}
this.videoCapturerRear.dispose();
}
this.camera.dispose();
if (this.videoSource != null) {
this.videoSource.dispose();
}
if (this.videoSourceRear != null) {
this.videoSourceRear.dispose();
}
this.audioSource.dispose();
this.peerConnection.close();
this.peerConnection.dispose();
@ -329,57 +278,6 @@ public class PeerConnectionWrapper {
return this.peerConnection.addIceCandidate(candidate);
}
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull Context context, boolean rear) {
boolean camera2EnumeratorIsSupported = false;
try {
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
} catch (final Throwable throwable) {
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
}
Log.w(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
CameraEnumerator enumerator;
if (camera2EnumeratorIsSupported) enumerator = new Camera2Enumerator(context);
else enumerator = new Camera1Enumerator(true);
String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
boolean isDesiredDirection =
rear ? enumerator.isBackFacing(deviceName)
: enumerator.isFrontFacing(deviceName);
if (isDesiredDirection) {
String direction = rear ? "rear" : "front";
Log.w(TAG, "Creating " + direction + " facing camera capturer.");
final CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
Log.w(TAG, "Found " + direction + " facing capturer: " + deviceName);
return videoCapturer;
}
}
}
for (String deviceName : deviceNames) {
boolean isDesiredDirection =
rear ? enumerator.isBackFacing(deviceName)
: enumerator.isFrontFacing(deviceName);
if (!isDesiredDirection) {
Log.w(TAG, "Creating other camera capturer.");
final CameraVideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
if (videoCapturer != null) {
Log.w(TAG, "Found other facing capturer: " + deviceName);
return videoCapturer;
}
}
}
Log.w(TAG, "Video capture not supported!");
return null;
}
private SessionDescription correctSessionDescription(SessionDescription sessionDescription) {
String updatedSdp = sessionDescription.description.replaceAll("(a=fmtp:111 ((?!cbr=).)*)\r?\n", "$1;cbr=1\r\n");
@ -397,4 +295,125 @@ public class PeerConnectionWrapper {
super(throwable);
}
}
private static class Camera implements CameraVideoCapturer.CameraSwitchHandler {
@Nullable
private final CameraVideoCapturer capturer;
private final CameraEventListener cameraEventListener;
private final int cameraCount;
private CameraState.Direction activeDirection;
private boolean enabled;
Camera(@NonNull Context context, @NonNull CameraEventListener cameraEventListener)
{
this.cameraEventListener = cameraEventListener;
CameraEnumerator enumerator = getCameraEnumerator(context);
cameraCount = enumerator.getDeviceNames().length;
CameraVideoCapturer capturerCandidate = createVideoCapturer(enumerator, FRONT);
if (capturerCandidate != null) {
activeDirection = FRONT;
} else {
capturerCandidate = createVideoCapturer(enumerator, BACK);
if (capturerCandidate != null) {
activeDirection = BACK;
} else {
activeDirection = NONE;
}
}
capturer = capturerCandidate;
}
void flip() {
if (capturer == null || cameraCount < 2) {
Log.w(TAG, "Tried to flip the camera, but we only have " + cameraCount + " of them.");
return;
}
activeDirection = PENDING;
capturer.switchCamera(this);
}
void setEnabled(boolean enabled) {
this.enabled = enabled;
if (capturer == null) {
return;
}
try {
if (enabled) {
capturer.startCapture(1280, 720, 30);
} else {
capturer.stopCapture();
}
} catch (InterruptedException e) {
Log.w(TAG, "Got interrupted while trying to stop video capture", e);
}
}
void dispose() {
if (capturer != null) {
capturer.dispose();
}
}
int getCount() {
return cameraCount;
}
@NonNull CameraState.Direction getActiveDirection() {
return enabled ? activeDirection : NONE;
}
@Nullable CameraVideoCapturer getCapturer() {
return capturer;
}
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator,
@NonNull CameraState.Direction direction)
{
String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
if ((direction == FRONT && enumerator.isFrontFacing(deviceName)) ||
(direction == BACK && enumerator.isBackFacing(deviceName)))
{
return enumerator.createCapturer(deviceName, null);
}
}
return null;
}
private @NonNull CameraEnumerator getCameraEnumerator(@NonNull Context context) {
boolean camera2EnumeratorIsSupported = false;
try {
camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(context);
} catch (final Throwable throwable) {
Log.w(TAG, "Camera2Enumator.isSupport() threw.", throwable);
}
Log.w(TAG, "Camera2 enumerator supported: " + camera2EnumeratorIsSupported);
return camera2EnumeratorIsSupported ? new Camera2Enumerator(context)
: new Camera1Enumerator(true);
}
@Override
public void onCameraSwitchDone(boolean isFrontFacing) {
activeDirection = isFrontFacing ? FRONT : BACK;
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
}
@Override
public void onCameraSwitchError(String errorMessage) {
Log.e(TAG, "onCameraSwitchError: " + errorMessage);
cameraEventListener.onCameraSwitchCompleted(new CameraState(getActiveDirection(), getCount()));
}
}
public interface CameraEventListener {
void onCameraSwitchCompleted(@NonNull CameraState newCameraState);
}
}