Explorar o código

Initial work on better permissions handling

Signed-off-by: Mario Danic <mario@lovelyhq.com>
Mario Danic %!s(int64=7) %!d(string=hai) anos
pai
achega
df462a00b9

+ 125 - 43
app/src/main/java/com/nextcloud/talk/activities/CallActivity.java

@@ -121,6 +121,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
 import io.reactivex.disposables.Disposable;
 import io.reactivex.disposables.Disposable;
 import io.reactivex.functions.BooleanSupplier;
 import io.reactivex.functions.BooleanSupplier;
 import io.reactivex.schedulers.Schedulers;
 import io.reactivex.schedulers.Schedulers;
+import me.zhanghai.android.effortlesspermissions.AfterPermissionDenied;
 import me.zhanghai.android.effortlesspermissions.EffortlessPermissions;
 import me.zhanghai.android.effortlesspermissions.EffortlessPermissions;
 import me.zhanghai.android.effortlesspermissions.OpenAppDetailsDialogFragment;
 import me.zhanghai.android.effortlesspermissions.OpenAppDetailsDialogFragment;
 import pub.devrel.easypermissions.AfterPermissionGranted;
 import pub.devrel.easypermissions.AfterPermissionGranted;
@@ -131,8 +132,16 @@ public class CallActivity extends AppCompatActivity {
     private static final String[] PERMISSIONS_CALL = {
     private static final String[] PERMISSIONS_CALL = {
             android.Manifest.permission.CAMERA,
             android.Manifest.permission.CAMERA,
             android.Manifest.permission.RECORD_AUDIO,
             android.Manifest.permission.RECORD_AUDIO,
-            Manifest.permission.MODIFY_AUDIO_SETTINGS
     };
     };
+
+    private static final String[] PERMISSIONS_CAMERA = {
+            Manifest.permission.CAMERA
+    };
+
+    private static final String[] PERMISSIONS_MICROPHONE = {
+            Manifest.permission.RECORD_AUDIO
+    };
+
     @BindView(R.id.pip_video_view)
     @BindView(R.id.pip_video_view)
     SurfaceViewRenderer pipVideoView;
     SurfaceViewRenderer pipVideoView;
     @BindView(R.id.full_screen_surface_view)
     @BindView(R.id.full_screen_surface_view)
@@ -173,6 +182,7 @@ public class CallActivity extends AppCompatActivity {
     VideoRenderer localRenderer;
     VideoRenderer localRenderer;
     EglBase rootEglBase;
     EglBase rootEglBase;
     boolean leavingCall = false;
     boolean leavingCall = false;
+    boolean inCall = false;
     BooleanSupplier booleanSupplier = () -> leavingCall;
     BooleanSupplier booleanSupplier = () -> leavingCall;
     Disposable signalingDisposable;
     Disposable signalingDisposable;
     Disposable pingDisposable;
     Disposable pingDisposable;
@@ -185,8 +195,11 @@ public class CallActivity extends AppCompatActivity {
     private String credentials;
     private String credentials;
     private List<MagicPeerConnectionWrapper> magicPeerConnectionWrapperList = new ArrayList<>();
     private List<MagicPeerConnectionWrapper> magicPeerConnectionWrapperList = new ArrayList<>();
 
 
-    private boolean videoOn = true;
-    private boolean audioOn = true;
+    private boolean videoOn = false;
+    private boolean audioOn = false;
+
+    private boolean cameraInitialized = false;
+    private boolean microphoneInitialized = false;
 
 
     private static int getSystemUiVisibility() {
     private static int getSystemUiVisibility() {
         int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
         int flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -214,6 +227,8 @@ public class CallActivity extends AppCompatActivity {
         callSession = "0";
         callSession = "0";
         credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
         credentials = ApiHelper.getCredentials(userEntity.getUsername(), userEntity.getToken());
 
 
+        basicInitialization();
+
         if (userUtils.getCurrentUser() != null && userUtils.getCurrentUser() != userEntity) {
         if (userUtils.getCurrentUser() != null && userUtils.getCurrentUser() != userEntity) {
             userUtils.createOrUpdateUser(userEntity.getUsername(),
             userUtils.createOrUpdateUser(userEntity.getUsername(),
                     userEntity.getToken(), userEntity.getBaseUrl(), null,
                     userEntity.getToken(), userEntity.getBaseUrl(), null,
@@ -265,7 +280,9 @@ public class CallActivity extends AppCompatActivity {
                 }
                 }
             }
             }
 
 
-            localMediaStream.videoTracks.get(0).setEnabled(enable);
+            if (localMediaStream.videoTracks.size() > 0) {
+                localMediaStream.videoTracks.get(0).setEnabled(enable);
+            }
 
 
             if (enable) {
             if (enable) {
                 pipVideoView.setVisibility(View.VISIBLE);
                 pipVideoView.setVisibility(View.VISIBLE);
@@ -278,7 +295,9 @@ public class CallActivity extends AppCompatActivity {
                 message = "audioOn";
                 message = "audioOn";
             }
             }
 
 
-            localMediaStream.audioTracks.get(0).setEnabled(enable);
+            if (localMediaStream.audioTracks.size() > 0) {
+                localMediaStream.audioTracks.get(0).setEnabled(enable);
+            }
         }
         }
 
 
         for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
         for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
@@ -288,15 +307,25 @@ public class CallActivity extends AppCompatActivity {
 
 
     @OnClick(R.id.call_control_microphone)
     @OnClick(R.id.call_control_microphone)
     public void onMicrophoneClick() {
     public void onMicrophoneClick() {
-        audioOn = !audioOn;
+        if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
+            audioOn = !audioOn;
 
 
-        if (audioOn) {
-            microphoneControlButton.setImageResource(R.drawable.ic_mic_white_24px);
+            if (audioOn) {
+                microphoneControlButton.setImageResource(R.drawable.ic_mic_white_24px);
+            } else {
+                microphoneControlButton.setImageResource(R.drawable.ic_mic_off_white_24px);
+            }
+
+            toggleMedia(audioOn, false);
+        } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_MICROPHONE)) {
+            // Some permission is permanently denied so we cannot request them normally.
+            OpenAppDetailsDialogFragment.show(
+                    R.string.nc_microphone_permission_permanently_denied,
+                    R.string.nc_permissions_settings, this);
         } else {
         } else {
-            microphoneControlButton.setImageResource(R.drawable.ic_mic_off_white_24px);
+            EffortlessPermissions.requestPermissions(this, R.string.nc_permissions_audio,
+                    100, PERMISSIONS_MICROPHONE);
         }
         }
-
-        toggleMedia(audioOn, false);
     }
     }
 
 
     @OnClick(R.id.call_control_hangup)
     @OnClick(R.id.call_control_hangup)
@@ -307,15 +336,26 @@ public class CallActivity extends AppCompatActivity {
 
 
     @OnClick(R.id.call_control_camera)
     @OnClick(R.id.call_control_camera)
     public void onCameraClick() {
     public void onCameraClick() {
-        videoOn = !videoOn;
+        if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
+            videoOn = !videoOn;
 
 
-        if (videoOn) {
-            cameraControlButton.setImageResource(R.drawable.ic_videocam_white_24px);
+            if (videoOn) {
+                cameraControlButton.setImageResource(R.drawable.ic_videocam_white_24px);
+            } else {
+                cameraControlButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
+            }
+
+            toggleMedia(videoOn, true);
+        } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_CAMERA)) {
+            // Some permission is permanently denied so we cannot request them normally.
+            OpenAppDetailsDialogFragment.show(
+                    R.string.nc_camera_permission_permanently_denied,
+                    R.string.nc_permissions_settings, this);
         } else {
         } else {
-            cameraControlButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
+            EffortlessPermissions.requestPermissions(this, R.string.nc_permissions_video,
+                    100, PERMISSIONS_CAMERA);
         }
         }
 
 
-        toggleMedia(videoOn, true);
     }
     }
 
 
 
 
@@ -391,20 +431,53 @@ public class CallActivity extends AppCompatActivity {
     @AfterPermissionGranted(100)
     @AfterPermissionGranted(100)
     private void checkPermissions() {
     private void checkPermissions() {
         if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) {
         if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) {
-            initializeEverything();
+            if (!cameraInitialized) {
+                cameraInitialization();
+            }
+
+            if (!microphoneInitialized) {
+                microphoneInitialization();
+            }
+
+            if (!inCall) {
+                startCall();
+            }
         } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this,
         } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this,
                 PERMISSIONS_CALL)) {
                 PERMISSIONS_CALL)) {
-            // Some permission is permanently denied so we cannot request them normally.
-            OpenAppDetailsDialogFragment.show(
-                    R.string.nc_permissions_permanently_denied,
-                    R.string.nc_permissions_settings, this);
+            if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
+                cameraInitialization();
+                onCameraClick();
+            } else if (!EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
+                cameraControlButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
+                cameraSwitchButton.setEnabled(false);
+                cameraSwitchButton.setAlpha(0.5f);
+            }
+
+            if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
+                microphoneInitialization();
+                onMicrophoneClick();
+            } else if (!EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
+                microphoneControlButton.setImageResource(R.drawable.ic_mic_off_white_24px);
+            }
+
+            if (!inCall) {
+                startCall();
+            }
+
         } else {
         } else {
             EffortlessPermissions.requestPermissions(this, R.string.nc_permissions,
             EffortlessPermissions.requestPermissions(this, R.string.nc_permissions,
                     100, PERMISSIONS_CALL);
                     100, PERMISSIONS_CALL);
         }
         }
     }
     }
 
 
-    private void initializeEverything() {
+    @AfterPermissionDenied(100)
+    private void onPermissionsDenied() {
+        if (!inCall) {
+            startCall();
+        }
+    }
+
+    private void basicInitialization() {
         //Initialize PeerConnectionFactory globals.
         //Initialize PeerConnectionFactory globals.
         PeerConnectionFactory.InitializationOptions initializationOptions = PeerConnectionFactory.InitializationOptions
         PeerConnectionFactory.InitializationOptions initializationOptions = PeerConnectionFactory.InitializationOptions
                 .builder(this)
                 .builder(this)
@@ -416,23 +489,11 @@ public class CallActivity extends AppCompatActivity {
         PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
         PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
         peerConnectionFactory = new PeerConnectionFactory(options);
         peerConnectionFactory = new PeerConnectionFactory(options);
 
 
-        videoCapturer = createCameraCapturer(cameraEnumerator);
-
         //Create MediaConstraints - Will be useful for specifying video and audio constraints.
         //Create MediaConstraints - Will be useful for specifying video and audio constraints.
         audioConstraints = new MediaConstraints();
         audioConstraints = new MediaConstraints();
         videoConstraints = new MediaConstraints();
         videoConstraints = new MediaConstraints();
 
 
-        //Create a VideoSource instance
-        videoSource = peerConnectionFactory.createVideoSource(videoCapturer);
-        localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource);
-
-        //create an AudioSource instance
-        audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
-        localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource);
-
         localMediaStream = peerConnectionFactory.createLocalMediaStream("NCMS");
         localMediaStream = peerConnectionFactory.createLocalMediaStream("NCMS");
-        localMediaStream.addTrack(localAudioTrack);
-        localMediaStream.addTrack(localVideoTrack);
 
 
         // Create and audio manager that will take care of audio routing,
         // Create and audio manager that will take care of audio routing,
         // audio modes, audio device enumeration etc.
         // audio modes, audio device enumeration etc.
@@ -448,14 +509,6 @@ public class CallActivity extends AppCompatActivity {
             }
             }
         });
         });
 
 
-        startVideoCapture();
-
-        //create a videoRenderer based on SurfaceViewRenderer instance
-        localRenderer = new VideoRenderer(fullScreenVideoView);
-        // And finally, with our VideoRenderer ready, we
-        // can add our renderer to the VideoTrack.
-        localVideoTrack.addRenderer(localRenderer);
-
         iceServers = new ArrayList<>();
         iceServers = new ArrayList<>();
 
 
         //create sdpConstraints
         //create sdpConstraints
@@ -464,13 +517,41 @@ public class CallActivity extends AppCompatActivity {
         sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
         sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
         sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
         sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
         sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
         sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
+    }
 
 
+    private void cameraInitialization() {
+        videoCapturer = createCameraCapturer(cameraEnumerator);
+
+        //Create a VideoSource instance
+        videoSource = peerConnectionFactory.createVideoSource(videoCapturer);
+        localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource);
+        localMediaStream.addTrack(localVideoTrack);
+
+        //create a videoRenderer based on SurfaceViewRenderer instance
+        localRenderer = new VideoRenderer(fullScreenVideoView);
+        // And finally, with our VideoRenderer ready, we
+        // can add our renderer to the VideoTrack.
+        localVideoTrack.addRenderer(localRenderer);
+
+        cameraInitialized = true;
+    }
+
+    private void microphoneInitialization() {
+        //create an AudioSource instance
+        audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
+        localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource);
+        localMediaStream.addTrack(localAudioTrack);
+
+        microphoneInitialized = true;
+    }
+
+    private void startCall() {
+        inCall = true;
         animateCallControls(false, 5000);
         animateCallControls(false, 5000);
         startPullingSignalingMessages(false);
         startPullingSignalingMessages(false);
         registerNetworkReceiver();
         registerNetworkReceiver();
     }
     }
 
 
-
     @OnClick({R.id.full_screen_surface_view, R.id.remote_renderers_layout})
     @OnClick({R.id.full_screen_surface_view, R.id.remote_renderers_layout})
     public void showCallControls() {
     public void showCallControls() {
         if (callControls.getVisibility() != View.VISIBLE) {
         if (callControls.getVisibility() != View.VISIBLE) {
@@ -810,6 +891,7 @@ public class CallActivity extends AppCompatActivity {
     private void hangup(boolean dueToNetworkChange) {
     private void hangup(boolean dueToNetworkChange) {
 
 
         leavingCall = true;
         leavingCall = true;
+        inCall = false;
         dispose(null);
         dispose(null);
 
 
         for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {
         for (int i = 0; i < magicPeerConnectionWrapperList.size(); i++) {

+ 2 - 2
app/src/main/res/layout/activity_call.xml

@@ -74,7 +74,7 @@
             android:layout_marginEnd="@dimen/margin_between_elements"
             android:layout_marginEnd="@dimen/margin_between_elements"
             android:layout_toEndOf="@id/call_control_hangup"
             android:layout_toEndOf="@id/call_control_hangup"
             android:background="?android:selectableItemBackgroundBorderless"
             android:background="?android:selectableItemBackgroundBorderless"
-            android:src="@drawable/ic_videocam_white_24px"/>
+            android:src="@drawable/ic_videocam_off_white_24px"/>
 
 
         <ImageButton
         <ImageButton
             android:id="@+id/call_control_microphone"
             android:id="@+id/call_control_microphone"
@@ -83,7 +83,7 @@
             android:layout_marginEnd="@dimen/margin_between_elements"
             android:layout_marginEnd="@dimen/margin_between_elements"
             android:layout_toEndOf="@id/call_control_camera"
             android:layout_toEndOf="@id/call_control_camera"
             android:background="?android:selectableItemBackgroundBorderless"
             android:background="?android:selectableItemBackgroundBorderless"
-            android:src="@drawable/ic_mic_white_24px"/>
+            android:src="@drawable/ic_mic_off_white_24px"/>
 
 
         <ImageButton
         <ImageButton
             android:id="@+id/call_control_switch_camera"
             android:id="@+id/call_control_switch_camera"

+ 11 - 2
app/src/main/res/values/strings.xml

@@ -80,9 +80,18 @@
     <string name="nc_more_contacts_selected">contacts selected</string>
     <string name="nc_more_contacts_selected">contacts selected</string>
 
 
     <!-- Permissions -->
     <!-- Permissions -->
-    <string name="nc_permissions">Permissions may need to be granted to establish a video call. Please click \"ALLOW\" in
-        the upcoming system dialog.</string>
+    <string name="nc_permissions">Permissions need to be granted to establish a video and/or audio call. Please
+        click \"ALLOW\" in the upcoming system dialog.</string>
+    <string name="nc_permissions_audio">Microphone permission needs to be granted to enable audio calls. Please
+        click \"ALLOW\" in the upcoming system dialog.</string>
+    <string name="nc_permissions_video">Camera permission needs to be granted to enable video calls. Please
+        click \"ALLOW\" in the upcoming system dialog.</string>
     <string name="nc_permissions_permanently_denied">To make video calls, grant \"Camera\" and \"Microphone\" permissions in the system settings.</string>
     <string name="nc_permissions_permanently_denied">To make video calls, grant \"Camera\" and \"Microphone\" permissions in the system settings.</string>
+    <string name="nc_camera_permission_permanently_denied">To enable video communication, grant \"Camera\" permission
+        the system
+        settings.</string>
+    <string name="nc_microphone_permission_permanently_denied">To enable voice communication, grant \"Microphone\"
+        permission the system settings.</string>
     <string name="nc_permissions_settings">Open settings</string>
     <string name="nc_permissions_settings">Open settings</string>
 
 
 </resources>
 </resources>