add microphone availability check

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java     | 37 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java         | 17 
src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java           | 15 
src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java |  1 
src/main/res/values/strings.xml                                           |  1 
5 files changed, 64 insertions(+), 7 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/services/AppRTCAudioManager.java 🔗

@@ -15,7 +15,10 @@ import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
 import android.os.Build;
 import android.support.annotation.Nullable;
 import android.util.Log;
@@ -111,6 +114,40 @@ public class AppRTCAudioManager {
         return new AppRTCAudioManager(context, speakerPhonePreference);
     }
 
+    public static boolean isMicrophoneAvailable(final Context context) {
+        AudioRecord audioRecord = null;
+        boolean available = true;
+        try {
+            final int sampleRate = 44100;
+            final int channel = AudioFormat.CHANNEL_IN_MONO;
+            final int format = AudioFormat.ENCODING_PCM_16BIT;
+            final int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channel, format);
+            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channel, format, bufferSize);
+            audioRecord.startRecording();
+            final short[] buffer = new short[bufferSize];
+            final int audioStatus = audioRecord.read(buffer, 0, bufferSize);
+            if (audioStatus == AudioRecord.ERROR_INVALID_OPERATION || audioStatus == AudioRecord.STATE_UNINITIALIZED)
+                available = false;
+        } catch (Exception e) {
+            available = false;
+        } finally {
+            release(audioRecord);
+
+        }
+        return available;
+    }
+
+    private static void release(final AudioRecord audioRecord) {
+        if (audioRecord == null) {
+            return;
+        }
+        try {
+            audioRecord.release();
+        } catch (Exception e) {
+            //ignore
+        }
+    }
+
     /**
      * This method is called when the proximity sensor reports a state change,
      * e.g. from "NEAR to FAR" or from "FAR to NEAR".

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -82,6 +82,7 @@ import eu.siacs.conversations.entities.Transferable;
 import eu.siacs.conversations.entities.TransferablePlaceholder;
 import eu.siacs.conversations.http.HttpDownloadConnection;
 import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.AppRTCAudioManager;
 import eu.siacs.conversations.services.MessageArchiveService;
 import eu.siacs.conversations.services.QuickConversationsService;
 import eu.siacs.conversations.services.XmppConnectionService;
@@ -1272,12 +1273,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
 
 
     private void triggerRtpSession(final String action) {
-        final Contact contact = conversation.getContact();
-        final Intent intent = new Intent(activity, RtpSessionActivity.class);
-        intent.setAction(action);
-        intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, contact.getAccount().getJid().toEscapedString());
-        intent.putExtra(RtpSessionActivity.EXTRA_WITH, contact.getJid().asBareJid().toEscapedString());
-        startActivity(intent);
+        if (AppRTCAudioManager.isMicrophoneAvailable(getActivity())) {
+            final Contact contact = conversation.getContact();
+            final Intent intent = new Intent(activity, RtpSessionActivity.class);
+            intent.setAction(action);
+            intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, contact.getAccount().getJid().toEscapedString());
+            intent.putExtra(RtpSessionActivity.EXTRA_WITH, contact.getJid().asBareJid().toEscapedString());
+            startActivity(intent);
+        } else {
+            Toast.makeText(getActivity(), R.string.microphone_unavailable, Toast.LENGTH_SHORT).show();
+        }
     }
 
     private void handleAttachmentSelection(MenuItem item) {

src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java 🔗

@@ -9,6 +9,7 @@ import android.databinding.DataBindingUtil;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.StringRes;
 import android.util.Log;
@@ -126,7 +127,19 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
         }
         if (PermissionUtils.hasPermission(this, permissions, REQUEST_ACCEPT_CALL)) {
             putScreenInCallMode();
+            checkRecorderAndAcceptCall();
+        }
+    }
+
+    private void checkRecorderAndAcceptCall() {
+        final long start = SystemClock.elapsedRealtime();
+        final boolean isMicrophoneAvailable = AppRTCAudioManager.isMicrophoneAvailable(this);
+        final long stop = SystemClock.elapsedRealtime();
+        Log.d(Config.LOGTAG, "checking microphone availability took " + (stop - start) + "ms");
+        if (isMicrophoneAvailable) {
             requireRtpConnection().acceptCall();
+        } else {
+            Toast.makeText(this, R.string.microphone_unavailable, Toast.LENGTH_SHORT).show();
         }
     }
 
@@ -247,7 +260,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
         super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         if (PermissionUtils.allGranted(grantResults)) {
             if (requestCode == REQUEST_ACCEPT_CALL) {
-                requireRtpConnection().acceptCall();
+                checkRecorderAndAcceptCall();
             }
         } else {
             @StringRes int res;

src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java 🔗

@@ -950,6 +950,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
                 Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state);
                 return;
             }
+            //we need to call close
             sendSessionTerminate(Reason.CONNECTIVITY_ERROR);
         } else {
             updateEndUserState();

src/main/res/values/strings.xml 🔗

@@ -914,6 +914,7 @@
     <string name="missed_call">Missed call</string>
     <string name="audio_call">Audio call</string>
     <string name="video_call">Video call</string>
+    <string name="microphone_unavailable">Microphone unavailable</string>
     <plurals name="view_users">
         <item quantity="one">View %1$d Participant</item>
         <item quantity="other">View %1$d Participants</item>