audio: fix audio switching

- Removes samplerate hack which fixed input but exposed requirement
  for output downsampling in the event of an core layer samplerate
  reduction. The audio session instance allows bluetooth after the
  daemon initializes, allowing the input samplerate to be correctly
  fetched.
- Sets the audio device to speakerphone or headset at app init and
  when a call is terminated so the ringtone can be heard.

Change-Id: I99ebaca55dfd7295801626b8fa4f64ea24a5abb8
Reviewed-by: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
diff --git a/Ring/Ring/AppDelegate.swift b/Ring/Ring/AppDelegate.swift
index 18b6615..723ec99 100644
--- a/Ring/Ring/AppDelegate.swift
+++ b/Ring/Ring/AppDelegate.swift
@@ -89,6 +89,9 @@
         SystemAdapter().registerConfigurationHandler()
         self.startDaemon()
 
+        // sets output device to whatever is currently available (either spk / headset)
+        self.audioService.startAVAudioSession()
+
         // disables hardware decoding
         self.videoService.setDecodingAccelerated(withState: false)
 
@@ -106,9 +109,6 @@
             })
             .disposed(by: self.disposeBag)
 
-        // set device to headset if present
-        self.audioService.overrideAudioRoute(.override)
-
         // themetize the app
         Chameleon.setGlobalThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
         Chameleon.setRingThemeUsingPrimaryColor(UIColor.ringMain, withSecondaryColor: UIColor.ringSecondary, andContentStyle: .light)
diff --git a/Ring/Ring/Calls/CallViewModel.swift b/Ring/Ring/Calls/CallViewModel.swift
index 7ccf574..dc86025 100644
--- a/Ring/Ring/Calls/CallViewModel.swift
+++ b/Ring/Ring/Calls/CallViewModel.swift
@@ -293,6 +293,9 @@
         }
         self.callService.hangUp(callId: call.callId)
             .subscribe(onCompleted: { [weak self] in
+                // switch to either spk or headset (if connected) for loud ringtone
+                // incase we were using rcv during the call
+                self?.audioService.setToRing()
                 self?.log.info("Call canceled")
                 }, onError: { [weak self] error in
                     self?.log.error("Failed to cancel the call")
@@ -300,6 +303,10 @@
     }
 
     func answerCall() -> Completable {
+        // switch to rcv if that's what we were last using
+        if !self.audioService.isHeadsetConnected.value && !self.audioService.isOutputToSpeaker.value {
+            self.audioService.overrideToReceiver()
+        }
         return self.callService.accept(call: call)
     }
 
@@ -308,10 +315,9 @@
         guard let account = self.accountService.currentAccount else {
             return
         }
-        if isAudioOnly {
+        // switch to rcv if audio only and no headset connected
+        if isAudioOnly && !self.audioService.isHeadsetConnected.value {
             self.audioService.overrideToReceiver()
-        } else {
-            self.audioService.overrideToSpeaker()
         }
         self.callService.placeCall(withAccount: account,
                                    toRingId: uri,
diff --git a/Ring/Ring/Services/AudioService.swift b/Ring/Ring/Services/AudioService.swift
index 26d8522..a510d94 100644
--- a/Ring/Ring/Services/AudioService.swift
+++ b/Ring/Ring/Services/AudioService.swift
@@ -27,7 +27,6 @@
     case bluetooth      = 1
     case headphones     = 2
     case receiver       = 3
-    case dummy          = 4
 }
 
 class AudioService {
@@ -54,6 +53,18 @@
             object: nil)
     }
 
+    func startAVAudioSession() {
+        do {
+            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord,
+                                                            with: AVAudioSessionCategoryOptions.allowBluetooth)
+            try AVAudioSession.sharedInstance().setActive(true)
+        } catch {
+            log.error("\(error)")
+        }
+        setToRing()
+    }
+
+    // swiftlint:disable force_cast
     @objc private func audioRouteChangeListener(_ notification: Notification) {
         let reasonRaw = notification.userInfo![AVAudioSessionRouteChangeReasonKey] as! UInt
         self.log.debug("Audio route change: \(reasonRaw)")
@@ -62,12 +73,13 @@
         }
         overrideAudioRoute(reason)
     }
+    // swiftlint:enable force_cast
 
     func overrideAudioRoute(_ reason: AVAudioSessionRouteChangeReason) {
         let wasHeadsetConnected = isHeadsetConnected.value
         let bluetoothConnected = bluetoothAudioConnected()
         let headphonesConnected = headphoneAudioConnected()
-        self.log.debug("Audio route status: bluetooth: \(bluetoothConnected), headphones: \(headphonesConnected)")
+        self.log.debug("Audio route override - reason: \(reason.rawValue), status: bluetooth: \(bluetoothConnected), headphones: \(headphonesConnected)")
         isHeadsetConnected.value = bluetoothConnected || headphonesConnected
         if reason == .override && !isHeadsetConnected.value {
             setAudioOutputDevice(port: OutputPortType.builtinspk)
@@ -80,10 +92,6 @@
                 let outputPort = isOutputToSpeaker.value ? OutputPortType.builtinspk : OutputPortType.receiver
                 setAudioOutputDevice(port: outputPort)
             }
-        } else if reason == .categoryChange && (isHeadsetConnected.value || !isOutputToSpeaker.value) {
-            // Hack switch to dummy device for first call using bluetooth/headset/receiver
-            // allowing the samplerate for the input bus to be correctly set
-            setAudioOutputDevice(port: OutputPortType.dummy)
         }
     }
 
@@ -98,6 +106,12 @@
         }
     }
 
+    func setToRing() {
+        if !isHeadsetConnected.value {
+            setAudioOutputDevice(port: OutputPortType.builtinspk)
+        }
+    }
+
     func overrideToSpeaker() {
         isOutputToSpeaker.value = true
         setAudioOutputDevice(port: OutputPortType.builtinspk)
diff --git a/Ring/Ring/Services/CallsService.swift b/Ring/Ring/Services/CallsService.swift
index 3db93dc..177d958 100644
--- a/Ring/Ring/Services/CallsService.swift
+++ b/Ring/Ring/Services/CallsService.swift
@@ -253,7 +253,7 @@
                 let accountID = call?.accountId
                 self.sendVCard(callID: callId, accountID: accountID!)
             }
-            
+
             //Emit the call to the observers
             self.currentCall.onNext(call!)
         }