swift background BLE scanning with scanForPeripheralsWithServices UUID not taken into account - swift2

I'm performing a BLE devices scan in a specific thread. but if I want my app to run in background, I need to specify to scanForPeripheralsWithServices a specific range of services.
when launching the following (with nil) :
func startScanning() {
print("Start scanning...")
if let central = centralManager {
central.scanForPeripheralsWithServices(nil, options: nil)
}
}
I can find my BLE device with UUID "B737D0E7-AF53-9B83-E5D2-922140A92E00", but as apple documentation mentioned it cannot be ruined as it is in background mode.
Then if I specify this UUID to the function:
func startScanning() {
print("Start scanning...")
if let central = centralManager {
let ble = [CBUUID(string: "B737D0E7-AF53-9B83-E5D2-922140A92E00")]
central.scanForPeripheralsWithServices(ble, options: nil)
}
}
scan is never called. do I miss something ?

Related

How to use CoreBluetooth framework from XCUITest

I'm having a project where I have to write automation for Apple Watch & iPhone app. I'm forced to use XCUITest with Swift because Appium is not yet supporting WatchOS as a testing target.
In my tests, at some points, I have to communicate with an external device via BLE. I wanted to use the CoreBluetooth framework, but I got no luck with that. I have a working CoreBT code that I've used to create a simple BLE chat app, but for some reason, that same code is not working when called from the UI test.
The main problem is that I'm not able to get CBCentralManager in a powered state:
[CoreBluetooth] API MISUSE: <CBCentralManager: 0x28175ce00> can only accept this command while in the powered on state
Later I checked the BT authorization state inside the test with cbCentralManager.authorization == .notDetermined, and it confirmed that authorization is not confirmed. That might be a problem why CBCentralManager is not powering up, but I'm not sure how to resolve it. The app I'm testing is able to use BT without any issues (has all permissions).
Is it even possible to use CoreBluetooth from UITests??
This is the test class code:
import CoreBluetooth
import XCTest
let peripheralName = "MY-BLE-DEVICE-NAME"
let service_First = CBUUID(string: "0000ffe0-0000-1000-8000-00805f9b34fb")
let readCharacteristic = CBUUID(string: "0000ffe1-0000-1000-8000-00805f9b34fb")
let writeCharacteristic = CBUUID(string: "0000ffe1-0000-1000-8000-00805f9b34fb")
var ble_characteristic: CBCharacteristic?
var ble_perip: CBPeripheral?
class myBLERemoteUITests : XCTestCase {
var cbCentralManager : CBCentralManager!
func wait(timeout: TimeInterval){
let exp = expectation(description: "Wait for X seconds")
let result = XCTWaiter.wait(for: [exp], timeout: timeout)
print("LOG - Timer started for " + String(timeout) + " seconds...")
if result == XCTWaiter.Result.timedOut {
print("LOG - Timer stopped after " + String(timeout) + " seconds...")
}else {
XCTFail("Delay interrupted")
}
}
func test_someBLEtest() throws {
let app = XCUIApplication()
app.launch()
// MARK: Some UI automation steps
app.buttons["Get Started"].tap()
let tablesQuery2 = app.tables
let shoeImage = tablesQuery2.cells["ItemA"].images["item"]
shoeImage.tap()
let shoe2 = tablesQuery2.cells["ItemB"].images["item"]
shoe2.tap()
wait(timeout: 10)
app.buttons["Finish"].tap()
// MARK: Part where I need to communicate with BLE device
// Initialize CBCentralManager
cbCentralManager = CBCentralManager.init(delegate: self, queue: nil) // --> getting API MISUSE mentioned above
wait(timeout: 10)
cbCentralManager = CBCentralManager.init(delegate: self, queue: nil) // --> getting API MISUSE mentioned above
wait(timeout: 10)
cbCentralManager = CBCentralManager.init() // --> getting [CoreBluetooth] XPC connection invalid
// MARK: I check CBCentral Manager Authorization state
if (cbCentralManager.authorization == .allowedAlways){
print("allowed")
}else if (cbCentralManager.authorization == .notDetermined){
print("not determined") // --> I get that authorization is not determined
}
}
}
// MARK: Over here I have all needed CoreBluetooth callback functions:
extension myBLERemoteUITests : CBPeripheralDelegate {
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
if let services = peripheral.services {
//discover characteristics of services
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if let charac = service.characteristics {
for characteristic in charac {
peripheral.setNotifyValue(true, for: characteristic)
if characteristic.uuid == writeCharacteristic {
ble_characteristic = characteristic
}
for newChar: CBCharacteristic in service.characteristics!{
peripheral.readValue(for: newChar)
}
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == readCharacteristic {
print("Read Value : \(characteristic)")
}
}
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if characteristic.uuid == writeCharacteristic {
print("WRITE VALUE : \(characteristic)")
}
}
}
extension myBLERemoteUITests : CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
central.scanForPeripherals(withServices: nil, options: nil)
print("Scanning...")
}
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
guard peripheral.name != nil else {return}
if peripheral.name! == peripheralName {
print("BLE Device Found!")
//stopScan
cbCentralManager.stopScan()
//connect
cbCentralManager.connect(peripheral, options: nil)
ble_perip = peripheral
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("Connected : \(peripheral.name ?? "No Name")")
peripheral.discoverServices([service_First])
//discover all service
//peripheral.discoverServices(nil)
peripheral.delegate = self
}
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
print("Disconnected : \(peripheral.name ?? "No Name")")
cbCentralManager.scanForPeripherals(withServices: nil, options: nil)
}
}

VOIP notification is being missing or delay

How do I know that my device is blocked from receiving VoIP notifications?
The application stops receiving VoIP notifications after receiving for 3-4 times. I understand that from iOS 13 VoIP notifications should be reported to CallKit. Even after reporting to CallKit, I'm going through this issue of not receiving VoIP notifications.
We have set apns-expiration to 0 and the priority to immediately(10).
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: #escaping () -> Void) {
dictPayload = payload.dictionaryPayload[K.KEY.APS] as? [String : Any]
if dictPayload![K.KEY.ALERTTYPE] as? String == K.KEY.VOIPCALL {
self.displayIncomingCall(uuid: appDelegate.uudiForCall, handle: (self.dictPayload!["handle"] as? String)!) { (error) in
}
CallProviderDelegate.sharedInstance.callDidReceiveIncomingCallfromKill(callInfo: self.dictPayload!)
} else if dictPayload![K.KEY.ALERTTYPE] as? String == K.KEY.PUSHTOTALK {
isPTTON = true
pjsua_set_no_snd_dev()
CallHandler.sharedCallManager()?.muteCall(true)
CallHandler.sharedCallManager()?.setAudioSessionSpeaker()
CallProviderDelegate.sharedInstance.callDidReceivePTTFromKIll(callFromName: dictPayload!["title"] as? String, callfromExt: dictPayload![K.KEY.BODY] as? String)
} else if dictPayload![K.KEY.ALERTTYPE] as? String == K.KEY.HANGUP {
isPTTON = false
CallProviderDelegate.sharedInstance.endCallFromPTT(endCallUDID: appDelegate.uudiForCall)
}
}
func displayIncomingCall(
uuid: UUID,
handle: String,
hasVideo: Bool = false,
completion: ((Error?) -> Void)?
) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value:(handle))
CallProviderDelegate.sharedInstance.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in })
}
XCODE: 11.3.1,
SWIFT: 4.2 &
iOS: 13.0 +
I am trying to figure out this issue since the last 2 months but not able to resolve it. Please help me
Thanks In Advance.
Actually, it seems that you aren't reporting a new incoming call for every VoIP push notification. It's true that when there is an active CallKit call, you can receive VoIP pushes without reporting a new incoming call, but it's not as simple as it might seem. Since CallKit and PushKit are asynchronous, you are not guaranteed that when you receive a push of type K.KEY.PUSHTOTALK or K.KEY.HANGUP the call has already started. Moreover, if dictPayload is nil, you fail to report a new incoming call.
Anyway, I think that the biggest problem in your code is that you're not calling the completion handler of the pushRegistry(:didReceiveIncomingPushWith...) method. You should do the following:
self.displayIncomingCall(uuid: appDelegate.uudiForCall, handle: (self.dictPayload!["handle"] as? String)!) { (error) in
completion() // <---
}
and
CallProviderDelegate.sharedInstance.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in
completion()
})
// or
CallProviderDelegate.sharedInstance.provider.reportNewIncomingCall(with: uuid, update: update, completion: completion)

Unable to Screenshare in Tokbox for iOS Mobile Client

We have an issue when we screenshare from iOS client. During screenshare, the invitees are unable to view other apps or screens when the publisher navigates to other application.
//Following is the code that we are using--
fileprivate func startScreenSharing() {
self.isSharingScreen = true
multipartyScreenSharer = OTMultiPartyCommunicator.init(view: UIApplication.shared.keyWindow)
multipartyScreenSharer?.dataSource = self
// publishOnly here is to avoid subscripting to those who already subscribed
multipartyScreenSharer?.isPublishOnly = true
publisherView?.isHidden = true
multipartyScreenSharer?.connect {
[unowned self](signal, remote, error) in
self.isSharingScreen = true
guard error == nil else {
self.dismiss(animated: true) {
SVProgressHUD.showError(withStatus: error!.localizedDescription)
}
return
}
if signal == .publisherCreated {
self.multipartyScreenSharer?.isPublishAudio = true
}
}
}
multipartyScreenSharer = OTMultiPartyCommunicator.init(view:UIApplication.shared.keyWindow)
We only can share the application window. Can someone explain how we can share besides the application window.
Thanks.
It is not straight forward to share other apps screens or home screen. You will need to implement screen sharing extension and implement OpenTok streaming in the extension.

In IOS 11, DeviceMotion in background stopped working

My app reports and records location, altitude, rotation and accelerometer data (DeviceMotion) while in the background. This works fine on ios 10.3.3. On IOS 11, I no longer have access motion data while the device is locked. Altitude data and location data is still streaming to the console, though.
Has something changed in IOS 11 that prevents me from accessing motion data or am I doing trying to access it in a way that Apple now blocks like OperationQueue.main
Here is how I'm starting motion updates. If the phone is unlocked, all works fine. If I locking the phone, no more updates.:
let motionManager = self.motionManager
if motionManager.isDeviceMotionAvailable {
motionUpdateInterval = 0.15
motionManager.deviceMotionUpdateInterval = motionUpdateInterval
motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: OperationQueue.main) {deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
I can't find anything about Motion background modes changing but it seems there must be a way otherwise RunKeeper, Strava will break. Can someone help me get this working again before IOS11 launch?
Thanks!
Also came across this problem.
Our solution was to ensure we have another background mode enabled and running (in our case location updates + audio) and restart core motion updates when switching background/foreground.
Code sample:
import UIKit
import CoreMotion
final class MotionDetector {
private let motionManager = CMMotionManager()
private let opQueue: OperationQueue = {
let o = OperationQueue()
o.name = "core-motion-updates"
return o
}()
private var shouldRestartMotionUpdates = false
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(appDidEnterBackground),
name: .UIApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(appDidBecomeActive),
name: .UIApplicationDidBecomeActive,
object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self,
name: .UIApplicationDidEnterBackground,
object: nil)
NotificationCenter.default.removeObserver(self,
name: .UIApplicationDidBecomeActive,
object: nil)
}
func start() {
self.shouldRestartMotionUpdates = true
self.restartMotionUpdates()
}
func stop() {
self.shouldRestartMotionUpdates = false
self.motionManager.stopDeviceMotionUpdates()
}
#objc private func appDidEnterBackground() {
self.restartMotionUpdates()
}
#objc private func appDidBecomeActive() {
self.restartMotionUpdates()
}
private func restartMotionUpdates() {
guard self.shouldRestartMotionUpdates else { return }
self.motionManager.stopDeviceMotionUpdates()
self.motionManager.startDeviceMotionUpdates(using: .xArbitraryZVertical, to: self.opQueue) { deviceMotion, error in
guard let deviceMotion = deviceMotion else { return }
print(deviceMotion)
}
}
}
The official 11.1 release fixed the issue and I've heard from iPhone 8 users that the original implementation is working for them.
The 11.2 beta has not broken anything.

How to indicate network activity in status bar

I use Alamofire for networking in my iOS application. I need to run this app in iOS 7+. I want to indicate network activity in status bar, so I created this struct:
struct ActivityManager {
static var activitiesCount = 0
static func addActivity() {
if activitiesCount == 0 {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
activitiesCount++
}
static func removeActivity() {
if activitiesCount > 0 {
activitiesCount--
if activitiesCount == 0 {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
}
}
}
But I don't know, where to call in code addActivity() and removeActivity() methods. I don't want to write them with every request. I want to, that they will be called automatically with every request.
I tried also use pure NSURLSession and NSURLSessionTask and extend them:
extension NSURLSessionTask {
func resumeWithActivity() {
ActivityManager.addAction()
self.resume()
}
}
public extension NSURLSession {
func OwnDataTaskWithRequest(request: NSURLRequest!, ownCompletionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask! {
return self.dataTaskWithRequest(request, completionHandler: { (data, response, error) in
ActivityManager.removeAction()
ownCompletionHandler!(data, response, error)
})
}
}
Then I used them like this:
var session: NSURLSession = NSURLSession(configuration: config, delegate: self, delegateQueue: nil)
session.OwnDataTaskWithRequest(request) { (data, response, error) -> Void in
// some logic here
}.resumeWithActivity()
But this approach doesn't work. In iOS 7 is NSURLSession extension not visible. I created a bug report for this (with sample project).
Can you please give me some advise, how to reach my goal? With or without Alamofire?
If you don't want to call your functions manually for every request and that you want to use Alamofire, I suggest you to improve it to add the network activity indicator feature.
Have a look at the source of Alamofire
You need to register 2 notification observers in your ActivityManager and then trigger the notifications at the relevant places either in Alamofire.Manager or in Alamofire.Request.
Also have a look at the source of AFNetworkActivityIndicatorManager which implement the feature you want in AFNetworking.

Resources