Call : Manage microphone detection

Change-Id: Ib21105b7e7ca6c19fa11d973b676389908415e4e
diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java b/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
index 9772cd4..e0e369a 100644
--- a/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/mvp/BaseFragment.java
@@ -28,6 +28,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Toast;
 
 import javax.inject.Inject;
 
@@ -37,9 +38,10 @@
 import cx.ring.account.RingAccountCreationFragment;
 import cx.ring.application.RingApplication;
 import cx.ring.dependencyinjection.RingInjectionComponent;
+import cx.ring.model.RingError;
 import cx.ring.utils.Log;
 
-public abstract class BaseFragment<T extends RootPresenter> extends Fragment {
+public abstract class BaseFragment<T extends RootPresenter> extends Fragment implements BaseView {
 
     protected static final String TAG = BaseFragment.class.getSimpleName();
 
@@ -82,6 +84,20 @@
         mUnbinder.unbind();
     }
 
+    public void displayErrorToast(int error) {
+        String errorString;
+        switch (error) {
+            case RingError.NO_MICROPHONE:
+                errorString = getString(R.string.call_error_no_microphone);
+                break;
+            default:
+                errorString = getString(R.string.generic_error);
+                break;
+        }
+
+        Toast.makeText(getActivity(), errorString, Toast.LENGTH_LONG).show();
+    }
+
     protected void initPresenter(T presenter) {
 
     }
diff --git a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
index d2b4b1a..aa2dfdc 100644
--- a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.java
@@ -20,11 +20,13 @@
 package cx.ring.services;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.ImageFormat;
 import android.graphics.Point;
 import android.hardware.Camera;
 import android.media.AudioManager;
+import android.media.MediaRecorder;
 import android.os.Build;
 import android.support.annotation.Nullable;
 import android.util.LongSparseArray;
@@ -32,6 +34,7 @@
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
 
+import java.io.File;
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.Collections;
@@ -86,6 +89,29 @@
         setDefaultVideoDevice(Integer.toString(cameraFront));
     }
 
+    public boolean hasMicrophone() {
+        PackageManager pm = mContext.getPackageManager();
+        boolean hasMicrophone = pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
+
+        if (!hasMicrophone) {
+            MediaRecorder recorder = new MediaRecorder();
+            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+            recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
+            recorder.setOutputFile(new File(mContext.getCacheDir(), "MediaUtil#micAvailTestFile").getAbsolutePath());
+            try {
+                recorder.prepare();
+                recorder.start();
+                hasMicrophone = true;
+            } catch (Exception exception) {
+                hasMicrophone = false;
+            }
+            recorder.release();
+        }
+
+        return hasMicrophone;
+    }
+
     @Override
     public boolean isSpeakerPhoneOn() {
         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java
index 1c933a3..cf07c3d 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java
@@ -22,12 +22,16 @@
 import android.os.Bundle;
 import android.support.v17.leanback.app.BrowseFragment;
 import android.view.View;
+import android.widget.Toast;
 
 import javax.inject.Inject;
 
+import cx.ring.R;
+import cx.ring.model.RingError;
+import cx.ring.mvp.BaseView;
 import cx.ring.mvp.RootPresenter;
 
-public class BaseBrowseFragment<T extends RootPresenter> extends BrowseFragment {
+public class BaseBrowseFragment<T extends RootPresenter> extends BrowseFragment implements BaseView {
 
     protected static final String TAG = BaseBrowseFragment.class.getSimpleName();
 
@@ -49,6 +53,20 @@
         presenter.unbindView();
     }
 
+    public void displayErrorToast(int error) {
+        String errorString;
+        switch (error) {
+            case RingError.NO_MICROPHONE:
+                errorString = getString(R.string.call_error_no_microphone);
+                break;
+            default:
+                errorString = getString(R.string.generic_error);
+                break;
+        }
+
+        Toast.makeText(getActivity(), errorString, Toast.LENGTH_LONG).show();
+    }
+
     protected void initPresenter(T presenter) {
 
     }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
index 2b828fb..1b19fbd 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.java
@@ -30,11 +30,13 @@
 import cx.ring.model.Account;
 import cx.ring.model.CallContact;
 import cx.ring.model.Conversation;
+import cx.ring.model.RingError;
 import cx.ring.model.ServiceEvent;
 import cx.ring.model.Uri;
 import cx.ring.mvp.RootPresenter;
 import cx.ring.services.AccountService;
 import cx.ring.services.ContactService;
+import cx.ring.services.HardwareService;
 import cx.ring.services.PresenceService;
 import cx.ring.tv.model.TVListViewModel;
 import cx.ring.utils.Observable;
@@ -46,27 +48,25 @@
     private static final String TAG = MainPresenter.class.getSimpleName();
 
     private AccountService mAccountService;
-
     private ConversationFacade mConversationFacade;
-
     private ContactService mContactService;
-
     private PresenceService mPresenceService;
-
+    private HardwareService mHardwareService;
     private ExecutorService mExecutor;
-
     private ArrayList<Conversation> mConversations;
 
     @Inject
     public MainPresenter(AccountService accountService,
                          ContactService contactService,
                          PresenceService presenceService,
+                         HardwareService hardwareService,
                          @Named("ApplicationExecutor") ExecutorService executor,
                          ConversationFacade conversationfacade) {
         mAccountService = accountService;
         mContactService = contactService;
         mPresenceService = presenceService;
         mConversationFacade = conversationfacade;
+        this.mHardwareService = hardwareService;
         mExecutor = executor;
         mConversations = new ArrayList<>();
     }
@@ -154,6 +154,11 @@
     }
 
     public void contactClicked(TVListViewModel item) {
+        if (!mHardwareService.hasMicrophone()) {
+            getView().displayErrorToast(RingError.NO_MICROPHONE);
+            return;
+        }
+
         String accountID = mAccountService.getCurrentAccount().getAccountID();
 
         String ringID = item.getCallContact().getPhones().get(0).getNumber().toString();
diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
index 56ce466..fdf25f1 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java
@@ -21,6 +21,7 @@
 
 import java.util.ArrayList;
 
+import cx.ring.model.RingError;
 import cx.ring.tv.model.TVListViewModel;
 
 public interface MainView {
@@ -33,6 +34,8 @@
 
     void callContact(String accountID, String ringID);
 
+    void displayErrorToast(int error);
+
     void displayAccountInfos(String address);
 
     void showExportDialog(String pAccountID);
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java b/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java
index d4a15ec..1ab83e9 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java
@@ -21,13 +21,18 @@
 
 import android.os.Bundle;
 import android.view.View;
+import android.widget.Toast;
 
 import javax.inject.Inject;
 
+import cx.ring.R;
+import cx.ring.model.RingError;
+import cx.ring.mvp.BaseView;
 import cx.ring.mvp.RootPresenter;
 import cx.ring.utils.Log;
 
-public class BaseSearchFragment<T extends RootPresenter> extends android.support.v17.leanback.app.SearchFragment {
+public class BaseSearchFragment<T extends RootPresenter> extends android.support.v17.leanback.app.SearchFragment
+        implements BaseView {
 
     protected static final String TAG = BaseSearchFragment.class.getSimpleName();
 
@@ -50,6 +55,21 @@
         presenter.unbindView();
     }
 
+    @Override
+    public void displayErrorToast(int error) {
+        String errorString;
+        switch (error) {
+            case RingError.NO_MICROPHONE:
+                errorString = getString(R.string.call_error_no_microphone);
+                break;
+            default:
+                errorString = getString(R.string.generic_error);
+                break;
+        }
+
+        Toast.makeText(getActivity(), errorString, Toast.LENGTH_LONG).show();
+    }
+
     protected void initPresenter(T presenter) {
 
     }
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java
index d9c1167..dff9407 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchPresenter.java
@@ -23,10 +23,12 @@
 
 import cx.ring.model.Account;
 import cx.ring.model.CallContact;
+import cx.ring.model.RingError;
 import cx.ring.model.ServiceEvent;
 import cx.ring.model.Uri;
 import cx.ring.mvp.RootPresenter;
 import cx.ring.services.AccountService;
+import cx.ring.services.HardwareService;
 import cx.ring.utils.NameLookupInputHandler;
 import cx.ring.utils.Observable;
 import cx.ring.utils.Observer;
@@ -36,15 +38,18 @@
 
     private static final String TAG = RingSearchPresenter.class.getSimpleName();
 
-    AccountService mAccountService;
+    private AccountService mAccountService;
+    private HardwareService mHardwareService;
 
     private NameLookupInputHandler mNameLookupInputHandler;
     private String mLastBlockchainQuery = null;
     private CallContact mCallContact;
 
     @Inject
-    public RingSearchPresenter(AccountService accountService) {
+    public RingSearchPresenter(AccountService accountService,
+                               HardwareService hardwareService) {
         this.mAccountService = accountService;
+        this.mHardwareService = hardwareService;
     }
 
     @Override
@@ -145,6 +150,11 @@
     }
 
     public void contactClicked(CallContact contact) {
+        if (!mHardwareService.hasMicrophone()) {
+            getView().displayErrorToast(RingError.NO_MICROPHONE);
+            return;
+        }
+
         getView().startCall(mAccountService.getCurrentAccount().getAccountID(), contact.getPhones().get(0).getNumber().toString());
     }
 }
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java
index c0dbd58..9c8c100 100644
--- a/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java
+++ b/ring-android/app/src/main/java/cx/ring/tv/search/RingSearchView.java
@@ -20,7 +20,7 @@
 package cx.ring.tv.search;
 
 import cx.ring.model.CallContact;
-import cx.ring.model.Uri;
+import cx.ring.model.RingError;
 
 public interface RingSearchView {
 
@@ -28,5 +28,7 @@
 
     void clearSearch();
 
+    void displayErrorToast(int error);
+
     void startCall(String accountID, String number);
 }
diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml
index 7418ee5..e57557b 100644
--- a/ring-android/app/src/main/res/values/strings.xml
+++ b/ring-android/app/src/main/res/values/strings.xml
@@ -214,4 +214,6 @@
     <string name="gallery_error_title">No Gallery app found</string>
     <string name="gallery_error_message">No application found on device to open Gallery</string>
 
+    <string name="generic_error">An unknown error occurred</string>
+
 </resources>
diff --git a/ring-android/app/src/main/res/values/strings_call.xml b/ring-android/app/src/main/res/values/strings_call.xml
index 49d4d86..59502a9 100644
--- a/ring-android/app/src/main/res/values/strings_call.xml
+++ b/ring-android/app/src/main/res/values/strings_call.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
 Copyright (C) 2004-2016 Savoir-faire Linux Inc.
 
 Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
@@ -21,6 +20,9 @@
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 -->
 <resources>
+    <!-- Call error -->
+    <string name="call_error_no_microphone">Call canceled. No microphone detected.</string>
+
     <!-- SipCalls -->
     <string name="call_human_state_searching">Searching</string>
     <string name="call_human_state_incoming">Incoming</string>