Signal-iOS/Signal/src/util/VolumeButtons.swift
2019-05-23 08:46:38 -07:00

248 lines
8.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation
protocol VolumeButtonObserver: class {
func didPressVolumeButton(with identifier: VolumeButtons.Identifier)
func didReleaseVolumeButton(with identifier: VolumeButtons.Identifier)
func didTapVolumeButton(with identifier: VolumeButtons.Identifier)
func didBeginLongPressVolumeButton(with identifier: VolumeButtons.Identifier)
func didCompleteLongPressVolumeButton(with identifier: VolumeButtons.Identifier)
func didCancelLongPressVolumeButton(with identifier: VolumeButtons.Identifier)
}
class VolumeButtons {
static let shared = VolumeButtons()
enum Identifier {
case up, down
}
private init?() {
// If for some reason the API were using goes away (for example, in
// a future iOS version) this class will never instantiate.
guard VolumeButtons.supportsListeningToEvents else { return nil }
}
deinit {
stopObservation()
}
// MARK: Observer Management
private var observers: [Weak<VolumeButtonObserver>] = []
func addObserver(observer: VolumeButtonObserver) {
AssertIsOnMainThread()
if observers.firstIndex(where: { $0.value === observer }) == nil {
observers.append(Weak(value: observer))
}
guard !observers.isEmpty else { return }
startObservation()
}
func removeObserver(_ observer: VolumeButtonObserver) {
AssertIsOnMainThread()
observers = observers.filter { $0.value !== observer }
guard observers.isEmpty else { return }
stopObservation()
}
func removeAllObservers() {
AssertIsOnMainThread()
observers = []
stopObservation()
}
private func startObservation() {
guard !VolumeButtons.isRegisteredForEvents else { return }
VolumeButtons.isRegisteredForEvents = true
registerForNotifications()
}
private func stopObservation() {
VolumeButtons.isRegisteredForEvents = false
unregisterForNotifications()
defer { resetLongPress() }
guard let longPressingButton = longPressingButton else { return }
notifyObserversOfCancelLongPress(with: longPressingButton)
}
private func notifyObserversOfTap(with identifier: Identifier) {
observers.forEach { observer in
observer.value?.didTapVolumeButton(with: identifier)
}
}
private func notifyObserversOfBeginLongPress(with identifier: Identifier) {
observers.forEach { observer in
observer.value?.didBeginLongPressVolumeButton(with: identifier)
}
}
private func notifyObserversOfCompleteLongPress(with identifier: Identifier) {
observers.forEach { observer in
observer.value?.didCompleteLongPressVolumeButton(with: identifier)
}
}
private func notifyObserversOfCancelLongPress(with identifier: Identifier) {
observers.forEach { observer in
observer.value?.didCancelLongPressVolumeButton(with: identifier)
}
}
private func notifyObserversOfPress(with identifier: Identifier) {
observers.forEach { observer in
observer.value?.didPressVolumeButton(with: identifier)
}
}
private func notifyObserversOfRelease(with identifier: Identifier) {
observers.forEach { observer in
observer.value?.didReleaseVolumeButton(with: identifier)
}
}
// MARK: Tap / long press handling
private var longPressTimer: Timer?
private var longPressingButton: Identifier?
// It's not possible for up and down to be pressed simulataneously
// (if you press the second button, the OS will end the press on
// the first), so it allows for simplified handling here.
private func didPressButton(with identifier: Identifier) {
longPressingButton = nil
longPressTimer?.invalidate()
longPressTimer = WeakTimer.scheduledTimer(
timeInterval: longPressDuration,
target: self,
userInfo: nil,
repeats: false
) { [weak self] _ in
self?.longPressingButton = identifier
self?.notifyObserversOfBeginLongPress(with: identifier)
self?.longPressTimer?.invalidate()
self?.longPressTimer = nil
}
notifyObserversOfPress(with: identifier)
}
private func didReleaseButton(with identifier: Identifier) {
if longPressingButton == identifier {
notifyObserversOfCompleteLongPress(with: identifier)
} else {
notifyObserversOfTap(with: identifier)
}
resetLongPress()
notifyObserversOfRelease(with: identifier)
}
private func resetLongPress() {
longPressTimer?.invalidate()
longPressTimer = nil
longPressingButton = nil
}
// MARK: Volume Event Registration
// let encodedSelectorString = "setWantsVolumeButtonEvents:".encodedForSelector
private static let volumeEventsSelector = Selector("BXYGaHIABgVnAX0HfnZTBwYGAQBWCHYABgVL".decodedForSelector!)
private(set) static var isRegisteredForEvents = false {
didSet {
setEventRegistration(isRegisteredForEvents)
}
}
private static func setEventRegistration(_ active: Bool) {
typealias Type = @convention(c) (AnyObject, Selector, Bool) -> Void
let implementation = class_getMethodImplementation(UIApplication.self, volumeEventsSelector)
let setRegistration = unsafeBitCast(implementation, to: Type.self)
setRegistration(UIApplication.shared, volumeEventsSelector, active)
}
private static var supportsListeningToEvents: Bool {
return UIApplication.shared.responds(to: volumeEventsSelector)
}
// MARK: Notification Handling
// let encodedDownDownNotificationName = "_UIApplicationVolumeDownButtonDownNotification".encodedForSelector
private let downDownNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZVAQkAUwcGBgEAVQEJAF8BBnp3enRyBnoBAA==".decodedForSelector!)
// let encodedDownUpNotificationName = "_UIApplicationVolumeDownButtonUpNotification".encodedForSelector
private let downUpNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZVAQkAUwcGBgEAZgJfAQZ6d3p0cgZ6AQA=".decodedForSelector!)
// let encodedUpDownNotificationName = "_UIApplicationVolumeUpButtonDownNotification".encodedForSelector
private let upDownNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZmAlMHBgYBAFUBCQBfAQZ6d3p0cgZ6AQA=".decodedForSelector!)
// let encodedUpUpNotificationName = "_UIApplicationVolumeUpButtonUpNotification".encodedForSelector
private let upUpNotificationName = Notification.Name("cGZaUgICfXp0cgZ6AQBnAX0HfnZmAlMHBgYBAGYCXwEGend6dHIGegEA".decodedForSelector!)
private let longPressDuration: TimeInterval = 0.5
private func registerForNotifications() {
NotificationCenter.default.addObserver(
self,
selector: #selector(didPressVolumeUp),
name: upDownNotificationName,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(didReleaseVolumeUp),
name: upUpNotificationName,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(didPressVolumeDown),
name: downDownNotificationName,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(didReleaseVolumeDown),
name: downUpNotificationName,
object: nil
)
}
private func unregisterForNotifications() {
NotificationCenter.default.removeObserver(self)
}
@objc func didPressVolumeUp(_ notification: Notification) {
didPressButton(with: .up)
}
@objc func didReleaseVolumeUp(_ notification: Notification) {
didReleaseButton(with: .up)
}
@objc func didPressVolumeDown(_ notification: Notification) {
didPressButton(with: .down)
}
@objc func didReleaseVolumeDown(_ notification: Notification) {
didReleaseButton(with: .down)
}
}