From 93c9c83ac593ed1cf0c4671d88370b4d0a48a261 Mon Sep 17 00:00:00 2001
From: Michael Kirk <michael@signal.org>
Date: Tue, 19 Mar 2019 10:45:36 -0700
Subject: [PATCH] Haptic feedback when remote enables video

---
 Signal/src/UserInterface/HapticFeedback.swift | 59 +++++++++++++++++++
 .../Call/CallViewController.swift             | 35 +++++++++--
 2 files changed, 90 insertions(+), 4 deletions(-)

diff --git a/Signal/src/UserInterface/HapticFeedback.swift b/Signal/src/UserInterface/HapticFeedback.swift
index 672e33803..2db0b0975 100644
--- a/Signal/src/UserInterface/HapticFeedback.swift
+++ b/Signal/src/UserInterface/HapticFeedback.swift
@@ -46,3 +46,62 @@ class ModernSelectionHapticFeedbackAdapter: NSObject, SelectionHapticFeedbackAda
         selectionFeedbackGenerator.prepare()
     }
 }
+
+enum NotificationHapticFeedbackType {
+    case error, success, warning
+}
+
+extension NotificationHapticFeedbackType {
+    var uiNotificationFeedbackType: UINotificationFeedbackType {
+        switch self {
+        case .error: return .error
+        case .success: return .success
+        case .warning: return .warning
+        }
+    }
+}
+
+protocol NotificationHapticFeedbackAdapter {
+    func notificationOccurred(_ notificationType: NotificationHapticFeedbackType)
+}
+
+class NotificationHapticFeedback: NotificationHapticFeedbackAdapter {
+
+    let adapter: NotificationHapticFeedbackAdapter
+
+    init() {
+        if #available(iOS 10, *) {
+            adapter = ModernNotificationHapticFeedbackAdapter()
+        } else {
+            adapter = LegacyNotificationHapticFeedbackAdapter()
+        }
+    }
+
+    func notificationOccurred(_ notificationType: NotificationHapticFeedbackType) {
+        adapter.notificationOccurred(notificationType)
+    }
+}
+
+@available(iOS 10.0, *)
+class ModernNotificationHapticFeedbackAdapter: NotificationHapticFeedbackAdapter {
+    let feedbackGenerator = UINotificationFeedbackGenerator()
+
+    init() {
+        feedbackGenerator.prepare()
+    }
+
+    func notificationOccurred(_ notificationType: NotificationHapticFeedbackType) {
+        feedbackGenerator.notificationOccurred(notificationType.uiNotificationFeedbackType)
+        feedbackGenerator.prepare()
+    }
+}
+
+class LegacyNotificationHapticFeedbackAdapter: NotificationHapticFeedbackAdapter {
+    func notificationOccurred(_ notificationType: NotificationHapticFeedbackType) {
+        vibrate()
+    }
+
+    private func vibrate() {
+        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
+    }
+}
diff --git a/Signal/src/ViewControllers/Call/CallViewController.swift b/Signal/src/ViewControllers/Call/CallViewController.swift
index 758914d33..f56a6936b 100644
--- a/Signal/src/ViewControllers/Call/CallViewController.swift
+++ b/Signal/src/ViewControllers/Call/CallViewController.swift
@@ -88,10 +88,6 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
         }
     }
 
-    override var preferredStatusBarStyle: UIStatusBarStyle {
-        return .lightContent
-    }
-
     // MARK: - Settings Nag Views
 
     var isShowingSettingsNag = false {
@@ -230,6 +226,10 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
         return .portrait
     }
 
+    override var preferredStatusBarStyle: UIStatusBarStyle {
+        return .lightContent
+    }
+
     // MARK: - Create Views
 
     func createViews() {
@@ -1035,6 +1035,11 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
 
         AssertIsOnMainThread()
 
+        guard localVideoView.captureSession != captureSession else {
+            Logger.debug("ignoring redundant update")
+            return
+        }
+
         localVideoView.captureSession = captureSession
         let isHidden = captureSession == nil
 
@@ -1058,7 +1063,9 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
 
     internal func updateRemoteVideoTrack(remoteVideoTrack: RTCVideoTrack?) {
         AssertIsOnMainThread()
+
         guard self.remoteVideoTrack != remoteVideoTrack else {
+            Logger.debug("ignoring redundant update")
             return
         }
 
@@ -1070,9 +1077,29 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
 
         shouldRemoteVideoControlsBeHidden = false
 
+        if remoteVideoTrack != nil {
+            playRemoteEnabledVideoHapticFeedback()
+        }
+
         updateRemoteVideoLayout()
     }
 
+    // MARK: Video Haptics
+
+    let feedbackGenerator = NotificationHapticFeedback()
+    var lastHapticTime: TimeInterval = CACurrentMediaTime()
+    func playRemoteEnabledVideoHapticFeedback() {
+        let currentTime = CACurrentMediaTime()
+        guard currentTime - lastHapticTime > 5 else {
+            Logger.debug("ignoring haptic feedback since it's too soon")
+            return
+        }
+        feedbackGenerator.notificationOccurred(.success)
+        lastHapticTime = currentTime
+    }
+
+    // MARK: - Dismiss
+
     internal func dismissIfPossible(shouldDelay: Bool, ignoreNag ignoreNagParam: Bool = false, completion: (() -> Void)? = nil) {
         callUIAdapter.audioService.delegate = nil