audio: route audio to bluetooth when possible

Change-Id: I2a446e8fb8114b036ee85c463c387ee2a42bd7f4
Tuleap: #1407
diff --git a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
index 20b4140..3e3c64b 100755
--- a/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
+++ b/ring-android/app/src/main/java/cx/ring/dependencyinjection/ServiceInjectionModule.java
@@ -50,7 +50,6 @@
 import cx.ring.services.PresenceService;
 import cx.ring.services.SharedPreferencesServiceImpl;
 import cx.ring.utils.Log;
-import cx.ring.utils.MediaManager;
 import dagger.Module;
 import dagger.Provides;
 
@@ -192,10 +191,4 @@
     ScheduledExecutorService provideScheduledExecutorService() {
         return Executors.newSingleThreadScheduledExecutor();
     }
-
-    @Provides
-    @Singleton
-    MediaManager provideMediaManager(Context context) {
-        return new MediaManager(context);
-    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
index d647376..2b4dd45 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
@@ -124,7 +124,6 @@
     private MenuItem dialPadBtn = null;
     private MenuItem changeScreenOrientationBtn = null;
 
-    // Screen wake lock for incoming call
     private PowerManager.WakeLock mScreenWakeLock;
     private DisplayManager.DisplayListener displayListener;
 
diff --git a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
index 4e4fd57..ecffda9 100644
--- a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
+++ b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java
@@ -43,11 +43,13 @@
 import cx.ring.daemon.StringVect;
 import cx.ring.service.OpenSlParams;
 import cx.ring.utils.Log;
-import cx.ring.utils.MediaManager;
 import cx.ring.utils.NetworkUtils;
 import cx.ring.utils.StringUtils;
 
-public class DeviceRuntimeServiceImpl extends DeviceRuntimeService {
+import cx.ring.utils.Ringer;
+import cx.ring.utils.BluetoothWrapper;
+
+public class DeviceRuntimeServiceImpl extends DeviceRuntimeService implements AudioManager.OnAudioFocusChangeListener, BluetoothWrapper.BluetoothChangeListener {
 
     private static final String TAG = DeviceRuntimeServiceImpl.class.getName();
     private static final String[] PROFILE_PROJECTION = new String[]{ContactsContract.Profile._ID,
@@ -61,13 +63,17 @@
     @Inject
     protected Context mContext;
 
-    @Inject
-    protected MediaManager mediaManager;
-
     private long mDaemonThreadId = -1;
 
+    private Ringer mRinger;
+    private AudioManager mAudioManager;
+    private BluetoothWrapper mBluetoothWrapper;
+
     @Override
     public void loadNativeLibrary() {
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mRinger = new Ringer(mContext);
+
         Future<Boolean> result = mExecutor.submit(new Callable<Boolean>() {
             @Override
             public Boolean call() throws Exception {
@@ -97,13 +103,20 @@
         mainHandler.post(new Runnable() {
             @Override
             public void run() {
-                mediaManager.obtainAudioFocus(isRinging);
+                if (mBluetoothWrapper == null) {
+                    mBluetoothWrapper = new BluetoothWrapper(mContext);
+                    mBluetoothWrapper.registerScoUpdate();
+                    mBluetoothWrapper.registerBtConnection();
+                    mBluetoothWrapper.setBluetoothChangeListener(DeviceRuntimeServiceImpl.this);
+                }
+
+                obtainAudioFocus(isRinging);
                 if (isRinging) {
-                    mediaManager.audioManager.setMode(AudioManager.MODE_RINGTONE);
-                    mediaManager.startRing();
+                    mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+                    startRinging();
                 } else {
-                    mediaManager.stopRing();
-                    mediaManager.audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+                    stopRinging();
+                    mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
                 }
             }
         });
@@ -111,8 +124,8 @@
 
     @Override
     public void closeAudioState() {
-        mediaManager.stopRing();
-        mediaManager.abandonAudioFocus();
+        stopRinging();
+        abandonAudioFocus();
     }
 
     @Override
@@ -185,7 +198,6 @@
         return ContextCompat.checkSelfPermission(mContext, permission) == PackageManager.PERMISSION_GRANTED;
     }
 
-
     @Override
     public void getHardwareAudioFormat(IntVect ret) {
         OpenSlParams audioParams = OpenSlParams.createInstance(mContext);
@@ -223,4 +235,111 @@
             ret.add(StringUtils.capitalize(manufacturer) + " " + model);
         }
     }
+
+    @Override
+    public void startRinging() {
+        mRinger.ring();
+    }
+
+    @Override
+    public boolean isSpeakerOn() {
+        return mAudioManager.isSpeakerphoneOn();
+    }
+
+    @Override
+    public void stopRinging() {
+        mRinger.stopRing();
+    }
+
+    @Override
+    public void onAudioFocusChange(int arg0) {
+        Log.i(TAG, "onAudioFocusChange " + arg0);
+    }
+
+    @Override
+    public void abandonAudioFocus() {
+        mAudioManager.abandonAudioFocus(this);
+        if (mAudioManager.isSpeakerphoneOn()) {
+            mAudioManager.setSpeakerphoneOn(false);
+        }
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+
+        if (mBluetoothWrapper != null) {
+            mBluetoothWrapper.unregister();
+            mBluetoothWrapper.setBluetoothOn(false);
+            mBluetoothWrapper = null;
+        }
+    }
+
+    @Override
+    public void obtainAudioFocus(boolean requestSpeakerOn) {
+
+        mAudioManager.requestAudioFocus(this, getInCallStream(mAudioManager.isBluetoothA2dpOn()), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+
+        if (mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth()) {
+            Log.d(TAG, "Try to enable bluetooth");
+            mBluetoothWrapper.setBluetoothOn(true);
+        } else if (!mAudioManager.isWiredHeadsetOn()) {
+            mAudioManager.setSpeakerphoneOn(requestSpeakerOn);
+        }
+    }
+
+    @Override
+    public void switchAudioToCurrentMode() {
+        mRinger.stopRing();
+        if (mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth()) {
+            routeToBTHeadset();
+        } else {
+            mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+        }
+    }
+
+    private void routeToBTHeadset() {
+        Log.d(TAG, "Try to enable bluetooth");
+        mAudioManager.setSpeakerphoneOn(false);
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        mBluetoothWrapper.setBluetoothOn(true);
+        mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+    }
+
+    @Override
+    public void toggleSpeakerphone() {
+        if (mAudioManager.isSpeakerphoneOn()) {
+            mAudioManager.setSpeakerphoneOn(!mAudioManager.isSpeakerphoneOn());
+            if (mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth()) {
+                routeToBTHeadset();
+            }
+        } else {
+            mAudioManager.setSpeakerphoneOn(true);
+        }
+    }
+
+    @Override
+    public void onBluetoothStateChanged(int status) {
+        Log.d(TAG, "bluetoothStateChanged to: " + status);
+        if (mAudioManager.getMode() == AudioManager.MODE_IN_COMMUNICATION) {
+            routeToBTHeadset();
+        }
+    }
+
+    /**
+     * Get the stream id for in call track. Can differ on some devices. Current device for which it's different :
+     *
+     * @return
+     */
+    public static int getInCallStream(boolean requestBluetooth) {
+        /* Archos 5IT */
+        if (android.os.Build.BRAND.equalsIgnoreCase("archos") && android.os.Build.DEVICE.equalsIgnoreCase("g7a")) {
+            // Since archos has no voice call capabilities, voice call stream is
+            // not implemented
+            // So we have to choose the good stream tag, which is by default
+            // falled back to music
+            return AudioManager.STREAM_MUSIC;
+        }
+        if (requestBluetooth) {
+            return 6; /* STREAM_BLUETOOTH_SCO -- Thx @Stefan for the contrib */
+        }
+
+        return AudioManager.STREAM_VOICE_CALL;
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/utils/BluetoothWrapper.java b/ring-android/app/src/main/java/cx/ring/utils/BluetoothWrapper.java
new file mode 100644
index 0000000..728c2fb
--- /dev/null
+++ b/ring-android/app/src/main/java/cx/ring/utils/BluetoothWrapper.java
@@ -0,0 +1,196 @@
+/**
+ * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
+ * This file is part of CSipSimple.
+ * <p>
+ * CSipSimple is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * If you own a pjsip commercial license you can also redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public License
+ * as an android library.
+ * <p>
+ * CSipSimple is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * <p>
+ * You should have received a copy of the GNU General Public License
+ * along with CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package cx.ring.utils;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.util.Log;
+
+import java.util.Set;
+
+
+public class BluetoothWrapper {
+
+    private static String TAG = BluetoothWrapper.class.getSimpleName();
+
+    protected Context mContext;
+    private AudioManager audioManager;
+    private boolean isBluetoothConnected = false;
+    private BluetoothAdapter bluetoothAdapter;
+    private boolean targetBt = false;
+
+    public interface BluetoothChangeListener {
+        void onBluetoothStateChanged(int status);
+    }
+
+    private BluetoothChangeListener btChangesListener;
+
+    public BluetoothWrapper(Context context) {
+        mContext = context;
+        audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        if (bluetoothAdapter == null) {
+            try {
+                bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+            } catch (RuntimeException e) {
+                Log.w(TAG, "Cant get default bluetooth adapter ", e);
+            }
+        }
+    }
+
+    public void setBluetoothChangeListener(BluetoothChangeListener listener) {
+        btChangesListener = listener;
+    }
+
+    public boolean isBTHeadsetConnected() {
+        return bluetoothAdapter != null && (bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) == BluetoothAdapter.STATE_CONNECTED);
+    }
+
+    private BroadcastReceiver mediaStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            Log.d(TAG, ">>> BT SCO state changed !!! ");
+            if (AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(action)) {
+                int status = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR);
+                Log.d(TAG, "BT SCO state changed : " + status + " target is " + targetBt);
+                audioManager.setBluetoothScoOn(targetBt);
+
+                if (status == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
+                    Log.d(TAG, "BT SCO state changed : CONNECTED");
+                    isBluetoothConnected = true;
+                } else if (status == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
+                    Log.d(TAG, "BT SCO state changed : DISCONNECTED");
+                    isBluetoothConnected = false;
+                } else {
+                    Log.d(TAG, "BT SCO state changed : " + status);
+                }
+            }
+        }
+    };
+
+    // Create a BroadcastReceiver for ACTION_FOUND.
+    private final BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            Log.d(TAG, "BT state changed");
+            String action = intent.getAction();
+            if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+                int status = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
+                if (status == 2) {
+                    Log.d(TAG, "BT device found");
+                    // Discovery has found a device. Get the BluetoothDevice
+                    // object and its info from the Intent.
+                    if (btChangesListener != null) {
+                        btChangesListener.onBluetoothStateChanged(status);
+                    }
+                }
+            }
+        }
+    };
+
+
+    public boolean canBluetooth() {
+        // Detect if any bluetooth a device is available for call
+        if (bluetoothAdapter == null) {
+            // Device does not support Bluetooth
+            return false;
+        }
+        boolean hasConnectedDevice = false;
+        //If bluetooth is on
+        if (bluetoothAdapter.isEnabled()) {
+
+            //We get all bounded bluetooth devices
+            // bounded is not enough, should search for connected devices....
+            Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
+            for (BluetoothDevice device : pairedDevices) {
+                BluetoothClass bluetoothClass = device.getBluetoothClass();
+                if (bluetoothClass != null) {
+                    int deviceClass = bluetoothClass.getDeviceClass();
+                    if (bluetoothClass.hasService(BluetoothClass.Service.RENDER) ||
+                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET ||
+                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO ||
+                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) {
+                        //And if any can be used as a audio handset
+                        hasConnectedDevice = true;
+                        break;
+                    }
+                }
+            }
+        }
+        boolean retVal = hasConnectedDevice && audioManager.isBluetoothScoAvailableOffCall();
+        Log.d(TAG, "Can I do BT ? " + retVal);
+        return retVal;
+    }
+
+    public void setBluetoothOn(boolean on) {
+        Log.d(TAG, "Try to turn BT " + on);
+        cx.ring.utils.Log.i(TAG, "mAudioManager.isBluetoothA2dpOn():" + audioManager.isBluetoothA2dpOn());
+        cx.ring.utils.Log.i(TAG, "mAudioManager.isBluetoothscoOn():" + audioManager.isBluetoothScoOn());
+
+        targetBt = on;
+        if (on != isBluetoothConnected) {
+            // BT SCO connection state is different from required activation
+            if (on) {
+                // First we try to connect
+                Log.d(TAG, "BT SCO on >>>");
+                audioManager.startBluetoothSco();
+            } else {
+                Log.d(TAG, "BT SCO off >>>");
+                // We stop to use BT SCO
+                audioManager.setBluetoothScoOn(false);
+                // And we stop BT SCO connection
+                audioManager.stopBluetoothSco();
+            }
+        } else if (on != audioManager.isBluetoothScoOn()) {
+            // BT SCO is already in desired connection state
+            // we only have to use it
+            audioManager.setBluetoothScoOn(on);
+        }
+    }
+
+    public void registerScoUpdate() {
+        Log.d(TAG, "Register BT media receiver");
+        mContext.registerReceiver(mediaStateReceiver, new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
+    }
+
+    public void registerBtConnection() {
+        Log.d(TAG, "Register BT connection");
+        mContext.registerReceiver(mBtReceiver, new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED));
+    }
+
+    public void unregister() {
+        try {
+            Log.d(TAG, "Unregister BT media receiver");
+            mContext.unregisterReceiver(mediaStateReceiver);
+            mContext.unregisterReceiver(mBtReceiver);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to unregister media state receiver", e);
+        }
+    }
+}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/Compatibility.java b/ring-android/app/src/main/java/cx/ring/utils/Compatibility.java
deleted file mode 100644
index e3fa24b..0000000
--- a/ring-android/app/src/main/java/cx/ring/utils/Compatibility.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- *  Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
- *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
- *
- *  Author: Regis Montoya <r3gis.3R@gmail.com>
- *  Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- *  Additional permission under GNU GPL version 3 section 7:
- *
- *  If you modify this program, or any covered work, by linking or
- *  combining it with the OpenSSL project's OpenSSL library (or a
- *  modified version of that library), containing parts covered by the
- *  terms of the OpenSSL or SSLeay licenses, Savoir-faire Linux Inc.
- *  grants you additional permission to convey the resulting work.
- *  Corresponding Source for a non-source form of such a combination
- *  shall include the source code for the parts of OpenSSL used as well
- *  as that of the covered work.
- */
-
-package cx.ring.utils;
-
-import android.media.AudioManager;
-
-@SuppressWarnings("deprecation")
-public final class Compatibility {
-
-    private Compatibility() {
-    }
-
-    /**
-     * Get the stream id for in call track. Can differ on some devices. Current device for which it's different :
-     * 
-     * @return
-     */
-    public static int getInCallStream(boolean requestBluetooth) {
-        /* Archos 5IT */
-        if (android.os.Build.BRAND.equalsIgnoreCase("archos") && android.os.Build.DEVICE.equalsIgnoreCase("g7a")) {
-            // Since archos has no voice call capabilities, voice call stream is
-            // not implemented
-            // So we have to choose the good stream tag, which is by default
-            // falled back to music
-            return AudioManager.STREAM_MUSIC;
-        }
-        if (requestBluetooth) {
-            return 6; /* STREAM_BLUETOOTH_SCO -- Thx @Stefan for the contrib */
-        }
-
-        // return AudioManager.STREAM_MUSIC;
-        return AudioManager.STREAM_VOICE_CALL;
-    }
-
-// --Commented out by Inspection START (17-05-08 17:51):
-//    private static boolean needToneWorkaround() {
-//        return android.os.Build.PRODUCT.toLowerCase().startsWith("gt-i5800") || android.os.Build.PRODUCT.toLowerCase().startsWith("gt-i5801")
-//                || android.os.Build.PRODUCT.toLowerCase().startsWith("gt-i9003");
-//    }
-// --Commented out by Inspection STOP (17-05-08 17:51)
-
-}
\ No newline at end of file
diff --git a/ring-android/app/src/main/java/cx/ring/utils/MediaManager.java b/ring-android/app/src/main/java/cx/ring/utils/MediaManager.java
deleted file mode 100644
index 5408792..0000000
--- a/ring-android/app/src/main/java/cx/ring/utils/MediaManager.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- *  Copyright (C) 2004-2016 Savoir-faire Linux Inc.
- *
- *  Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
- *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 3 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-package cx.ring.utils;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.os.Handler;
-import android.util.Log;
-import cx.ring.utils.bluetooth.BluetoothWrapper;
-
-public class MediaManager implements OnAudioFocusChangeListener, BluetoothWrapper.BluetoothChangeListener {
-
-    private static final String TAG = MediaManager.class.getSimpleName();
-    public final AudioManager audioManager;
-    private final Ringer ringer;
-
-    public MediaManager(Context c) {
-        audioManager = (AudioManager) c.getSystemService(Context.AUDIO_SERVICE);
-        
-        ringer = new Ringer(c);
-    }
-
-    public void obtainAudioFocus(boolean requestSpeakerOn) {
-        audioManager.requestAudioFocus(this, Compatibility.getInCallStream(false), AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-        if (!audioManager.isWiredHeadsetOn()){
-            audioManager.setSpeakerphoneOn(requestSpeakerOn);
-        }
-    }
-
-    @Override
-    public void onAudioFocusChange(int arg0) {
-        Log.i(TAG, "onAudioFocusChange " + arg0);
-    }
-
-    public void abandonAudioFocus() {
-        audioManager.abandonAudioFocus(this);
-        if (audioManager.isSpeakerphoneOn()) {
-            audioManager.setSpeakerphoneOn(false);
-        }
-        audioManager.setMode(AudioManager.MODE_NORMAL);
-    }
-
-    /**5
-     * Start ringing announce for a given contact.
-     * It will also focus audio for us.
-     */
-    synchronized public void startRing() {
-        ringer.ring();
-    }
-    
-    /**
-     * Stop all ringing. <br/>
-     * Warning, this will not unfocus audio.
-     */
-    synchronized public void stopRing() {
-        ringer.stopRing();
-    }
-
-    @Override
-    public void onBluetoothStateChanged(int status) {
-    }
-
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/Ringer.java b/ring-android/app/src/main/java/cx/ring/utils/Ringer.java
index ef3ef9f..2b01491 100644
--- a/ring-android/app/src/main/java/cx/ring/utils/Ringer.java
+++ b/ring-android/app/src/main/java/cx/ring/utils/Ringer.java
@@ -63,15 +63,13 @@
         if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
             //No ring no vibrate
             Log.d(TAG, "skipping ring and vibrate because profile is Silent");
-        }
-        else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE || ringerMode == AudioManager.RINGER_MODE_NORMAL) {
+        } else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE || ringerMode == AudioManager.RINGER_MODE_NORMAL) {
             // Vibrate
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                 vibrator.vibrate(VIBRATE_PATTERN, 0, VIBRATE_ATTRIBUTES);
             } else {
                 vibrator.vibrate(VIBRATE_PATTERN, 0);
             }
-            audioManager.setMode(AudioManager.MODE_RINGTONE);
         }
     }
 
diff --git a/ring-android/app/src/main/java/cx/ring/utils/bluetooth/BluetoothUtils14.java b/ring-android/app/src/main/java/cx/ring/utils/bluetooth/BluetoothUtils14.java
deleted file mode 100644
index c378347..0000000
--- a/ring-android/app/src/main/java/cx/ring/utils/bluetooth/BluetoothUtils14.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/**
- * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
- * This file is part of CSipSimple.
- *
- *  CSipSimple is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  If you own a pjsip commercial license you can also redistribute it
- *  and/or modify it under the terms of the GNU Lesser General Public License
- *  as an android library.
- *
- *  CSipSimple is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package cx.ring.utils.bluetooth;
-
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.util.Log;
-
-import java.util.Set;
-
-public class BluetoothUtils14 extends BluetoothWrapper {
-
-    private static String TAG = BluetoothUtils14.class.getSimpleName();
-    private AudioManager audioManager;
-    private boolean isBluetoothConnected = false;
-    
-    
-    @Override
-    public boolean isBTHeadsetConnected() {
-        return bluetoothAdapter != null && (bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) == BluetoothAdapter.STATE_CONNECTED);
-    }
-
-    
-
-    private BroadcastReceiver mediaStateReceiver = new BroadcastReceiver() {
-
-        @SuppressWarnings("deprecation")
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            Log.d(TAG, ">>> BT SCO state changed !!! ");
-            if(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED.equals(action)) {
-                int status = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, AudioManager.SCO_AUDIO_STATE_ERROR );
-                Log.d(TAG, "BT SCO state changed : " + status + " target is " + targetBt);
-                audioManager.setBluetoothScoOn(targetBt);
-
-                if(status == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
-                    isBluetoothConnected = true;
-                }else if(status == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
-                    isBluetoothConnected = false;
-                }
-
-                if(btChangesListener != null) {
-                    btChangesListener.onBluetoothStateChanged(status);
-                }
-            }
-        }
-    };
-
-    protected BluetoothAdapter bluetoothAdapter;
-
-    @Override
-    public void setContext(Context aContext){
-        super.setContext(aContext);
-        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        if(bluetoothAdapter == null) {
-            try {
-                bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-            }catch(RuntimeException e) {
-                Log.w(TAG, "Cant get default bluetooth adapter ", e);
-            }
-        }
-    }
-
-    public boolean canBluetooth() {
-        // Detect if any bluetooth a device is available for call
-        if (bluetoothAdapter == null) {
-            // Device does not support Bluetooth
-            return false;
-        }
-        boolean hasConnectedDevice = false;
-        //If bluetooth is on
-        if(bluetoothAdapter.isEnabled()) {
-
-            //We get all bounded bluetooth devices
-            // bounded is not enough, should search for connected devices....
-            Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
-            for(BluetoothDevice device : pairedDevices) {
-                BluetoothClass bluetoothClass = device.getBluetoothClass();
-                if (bluetoothClass != null) {
-                    int deviceClass = bluetoothClass.getDeviceClass();
-                    if(bluetoothClass.hasService(BluetoothClass.Service.RENDER) ||
-                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET ||
-                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO ||
-                            deviceClass == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ) {
-                        //And if any can be used as a audio handset
-                        hasConnectedDevice = true;
-                        break;
-                    }
-                }
-            }
-        }
-        boolean retVal = hasConnectedDevice && audioManager.isBluetoothScoAvailableOffCall();
-        Log.d(TAG, "Can I do BT ? "+retVal);
-        return retVal;
-    }
-
-    private boolean targetBt = false;
-    public void setBluetoothOn(boolean on) {
-        Log.d(TAG, "Ask for "+on+" vs "+audioManager.isBluetoothScoOn());
-        targetBt = on;
-        if(on != isBluetoothConnected) {
-            // BT SCO connection state is different from required activation
-            if(on) {
-                // First we try to connect
-                Log.d(TAG, "BT SCO on >>>");
-                audioManager.startBluetoothSco();
-            }else {
-                Log.d(TAG, "BT SCO off >>>");
-                // We stop to use BT SCO
-                audioManager.setBluetoothScoOn(false);
-                // And we stop BT SCO connection
-                audioManager.stopBluetoothSco();
-            }
-        }else if(on != audioManager.isBluetoothScoOn()) {
-            // BT SCO is already in desired connection state
-            // we only have to use it
-            audioManager.setBluetoothScoOn(on);
-        }
-    }
-
-    public boolean isBluetoothOn() {
-        return isBluetoothConnected;
-    }
-
-    @SuppressWarnings("deprecation")
-    public void register() {
-        Log.d(TAG, "Register BT media receiver");
-        context.registerReceiver(mediaStateReceiver , new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED));
-    }
-
-    public void unregister() {
-        try {
-            Log.d(TAG, "Unregister BT media receiver");
-            context.unregisterReceiver(mediaStateReceiver);
-        }catch(Exception e) {
-            Log.w(TAG, "Failed to unregister media state receiver",e);
-        }
-    }
-}
diff --git a/ring-android/app/src/main/java/cx/ring/utils/bluetooth/BluetoothWrapper.java b/ring-android/app/src/main/java/cx/ring/utils/bluetooth/BluetoothWrapper.java
deleted file mode 100644
index 053db85..0000000
--- a/ring-android/app/src/main/java/cx/ring/utils/bluetooth/BluetoothWrapper.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/**
- * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
- * This file is part of CSipSimple.
- *
- *  CSipSimple is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 of the License, or
- *  (at your option) any later version.
- *  If you own a pjsip commercial license you can also redistribute it
- *  and/or modify it under the terms of the GNU Lesser General Public License
- *  as an android library.
- *
- *  CSipSimple is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with CSipSimple.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package cx.ring.utils.bluetooth;
-
-import android.content.Context;
-
-
-public abstract class BluetoothWrapper {
-
-    public interface BluetoothChangeListener {
-        void onBluetoothStateChanged(int status);
-    }
-
-
-    private static BluetoothWrapper instance;
-    protected Context context;
-
-    protected BluetoothChangeListener btChangesListener;
-
-    public static BluetoothWrapper getInstance(Context context) {
-        if (instance == null) {
-            instance = new BluetoothUtils14();
-            instance.setContext(context);
-        }
-
-        return instance;
-    }
-
-    protected BluetoothWrapper() {
-    }
-
-    protected void setContext(Context ctxt) {
-        context = ctxt;
-    }
-
-    public void setBluetoothChangeListener(BluetoothChangeListener l) {
-        btChangesListener = l;
-    }
-
-    public abstract boolean canBluetooth();
-
-    public abstract void setBluetoothOn(boolean on);
-
-    public abstract boolean isBluetoothOn();
-
-    public abstract void register();
-
-    public abstract void unregister();
-
-    public abstract boolean isBTHeadsetConnected();
-}
diff --git a/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java b/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java
index e2630f2..98d4bea 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/call/CallPresenter.java
@@ -214,6 +214,9 @@
     }
 
     public void videoSurfaceCreated(Object holder) {
+        if (mSipCall == null) {
+            return;
+        }
         mHardwareService.addVideoSurface(mSipCall.getCallId(), holder);
         getView().displayContactBubble(false);
     }
@@ -249,6 +252,7 @@
 
     private void finish() {
         if (mSipCall != null) {
+            mCallService.hangUp(mSipCall.getCallId());
             mNotificationService.cancelCallNotification(mSipCall.getCallId().hashCode());
         }
         if (executor != null && !executor.isShutdown()) {
diff --git a/ring-android/libringclient/src/main/java/cx/ring/services/DeviceRuntimeService.java b/ring-android/libringclient/src/main/java/cx/ring/services/DeviceRuntimeService.java
index c13dfcf..405b928 100644
--- a/ring-android/libringclient/src/main/java/cx/ring/services/DeviceRuntimeService.java
+++ b/ring-android/libringclient/src/main/java/cx/ring/services/DeviceRuntimeService.java
@@ -55,4 +55,17 @@
 
     public abstract String getProfileName();
 
+    public abstract void startRinging();
+
+    public abstract boolean isSpeakerOn();
+
+    public abstract void stopRinging();
+
+    public abstract void abandonAudioFocus();
+
+    public abstract void obtainAudioFocus(boolean requesSpeakerOn);
+
+    public abstract void switchAudioToCurrentMode();
+
+    public abstract void toggleSpeakerphone();
 }