Skip to content

Commit

Permalink
Local push on iOS (#1656)
Browse files Browse the repository at this point in the history
Refs #1570 and home-assistant/core#50750. Fixes #1382.

## Summary
Adds a new app extension to do local push notifications on iOS 14 when connected to internal SSIDs.

## Screenshots
Adds a default-on setting to Internal URL:

| Light | Dark |
| -- | -- |
|  ![Simulator Screen Shot - iPhone 12 Pro - 2021-06-19 at 23 13 04](https://user-images.githubusercontent.com/74188/122664142-5cd73d80-d154-11eb-8378-600f0b82b3e4.png) | ![Simulator Screen Shot - iPhone 12 Pro - 2021-06-19 at 23 13 06](https://user-images.githubusercontent.com/74188/122664145-62cd1e80-d154-11eb-840d-0a0e86255bcb.png) |

## Link to pull request in Documentation repository
Documentation: home-assistant/companion.home-assistant#539

## Any other notes
- Updates the "you need always permission" warning in Internal URL editing to be vibrantly red, to really point out its importance.
- Sets the code signing for the app and the push target to 'manual' on device, hopefully for our internal team only. Special entitlements really do not play well with open source. Worth noting that it is not possible to test this feature without being on the HA team since it does not work in simulator (as far as I can tell) and running on-device requires entitlements.
- Moves commands into Shared in a slightly-easier registration mechanism, so we can share them in the local push extension.
  • Loading branch information
zacwest authored Jun 20, 2021
1 parent 3ede2d3 commit 1db6f3c
Show file tree
Hide file tree
Showing 51 changed files with 1,055 additions and 140 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jobs:
- uses: actions/cache@v2
name: "Cache: Gems"
id: cache_gems
if: matrix.xcode == '12.5'
with:
path: vendor/bundle
key: >-
Expand All @@ -104,7 +105,7 @@ jobs:
run: bundle exec pod install --repo-update

- name: Install Pods Beta
if: steps.cache_pods.outputs.cache-hit != 'true' && matrix.xcode == '13.0'
if: matrix.xcode == '13.0'
run: XCODE_BETA=true bundle exec pod install --repo-update

- name: Run tests
Expand Down
20 changes: 20 additions & 0 deletions Configuration/Entitlements/activate_special_entitlements.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash

if [[ $CI && $CONFIGURATION != "Release" ]]; then
echo "warning: Critical alerts disabled for CI"
elif [[ ${ENABLE_CRITICAL_ALERTS} -eq 1 ]]; then
entitlements_file="${TARGET_TEMP_DIR}/${FULL_PRODUCT_NAME}.xcent"
/usr/libexec/PlistBuddy -c "add com.apple.developer.usernotifications.critical-alerts bool true" "$entitlements_file"
else
echo "warning: Critical alerts disabled"
fi

if [[ $CI && $CONFIGURATION != "Release" ]]; then
echo "warning: Push provider disabled for CI"
elif [[ ${ENABLE_PUSH_PROVIDER} -eq 1 ]]; then
entitlements_file="${TARGET_TEMP_DIR}/${FULL_PRODUCT_NAME}.xcent"
/usr/libexec/PlistBuddy -c "add com.apple.developer.networking.networkextension array" "$entitlements_file"
/usr/libexec/PlistBuddy -c "add com.apple.developer.networking.networkextension:0 string 'app-push-provider'" "$entitlements_file"
else
echo "warning: Push provider disabled"
fi
3 changes: 0 additions & 3 deletions Configuration/HomeAssistant.release.xcconfig
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// indirect so external team changes don't require entitlement
ENABLE_CRITICAL_ALERTS_QMQYCKL255 = 1

#include "HomeAssistant.xcconfig"

CODE_SIGN_STYLE = Manual
Expand Down
13 changes: 12 additions & 1 deletion Configuration/HomeAssistant.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ VERSIONING_SYSTEM = apple-generic

DEVELOPMENT_TEAM = QMQYCKL255
BUNDLE_ID_PREFIX = io.robbie
ENABLE_CRITICAL_ALERTS_QMQYCKL255 = 1
ENABLE_PUSH_PROVIDER_QMQYCKL255 = 1
CODE_SIGN_STYLE_QMQYCKL255_App = Manual
CODE_SIGN_STYLE_QMQYCKL255_Extensions_PushProvider = Manual

// cascades down
PRODUCT_BUNDLE_IDENTIFIER = ${BUNDLE_ID_PREFIX}.HomeAssistant${BUNDLE_ID_SUFFIX}${PROVISIONING_SUFFIX}
Expand All @@ -15,14 +19,21 @@ DEAD_CODE_STRIPPING[sdk=watchos*] = NO

// release builds override this
CODE_SIGN_IDENTITY = Apple Development
CODE_SIGN_STYLE = Automatic
CODE_SIGN_STYLE_DEFAULT = Automatic

// Create this file to override configuration. It's ignored by git
#include? "HomeAssistant.overrides.xcconfig"

// set after overrides in case the team is changed by them - this is customized in e.g. the Release xcconfig
// only iOS, not catalyst, because no critical alerts provisioning for us exists on mac
ENABLE_CRITICAL_ALERTS[sdk=iphoneos*] = $(ENABLE_CRITICAL_ALERTS_$(DEVELOPMENT_TEAM))
ENABLE_PUSH_PROVIDER[sdk=iphoneos*] = $(ENABLE_PUSH_PROVIDER_$(DEVELOPMENT_TEAM))

// We mutate the entitlements at build time to support other development teams
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES
// Mutate certain targets' default signing style because we need manual on ios sdk
CODE_SIGN_STYLE = $(CODE_SIGN_STYLE_DEFAULT)
CODE_SIGN_STYLE[sdk=iphoneos*] = $(CODE_SIGN_STYLE_$(DEVELOPMENT_TEAM)_$(TARGET_NAME:c99extidentifier):default=$CODE_SIGN_STYLE_DEFAULT)

// Baseline information
SDKROOT = iphoneos
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
259 changes: 253 additions & 6 deletions HomeAssistant.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1230"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
wasCreatedForAppExtension = "YES"
version = "1.3">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1230"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1220"
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
1 change: 1 addition & 0 deletions Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ abstract_target 'iOS' do
target 'Extensions-Intents'
target 'Extensions-NotificationContent'
target 'Extensions-NotificationService'
target 'Extensions-PushProvider'
target 'Extensions-Share'
target 'Extensions-Today'
target 'Extensions-Widgets'
Expand Down
2 changes: 1 addition & 1 deletion Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,6 @@ SPEC CHECKSUMS:
XCGLogger: 1943831ef907df55108b0b18657953f868de973b
ZIPFoundation: e27423c004a5a1410c15933407747374e7c6cb6e

PODFILE CHECKSUM: 0cfa9045eab67b90a46d0fe392e27110979af7a3
PODFILE CHECKSUM: 45c7f2fb7efd0ee4c97588afb1529924647dd8a7

COCOAPODS: 1.10.1
94 changes: 15 additions & 79 deletions Sources/App/Notifications/NotificationManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,21 @@ import UserNotifications
import XCGLogger

class NotificationManager: NSObject, LocalPushManagerDelegate {
static var didUpdateComplicationsNotification: Notification.Name {
.init(rawValue: "didUpdateComplicationsNotification")
}

var localPushManager: LocalPushManager? {
didSet {
precondition(Current.isCatalyst)
lazy var localPushManager: NotificationManagerLocalPushInterface = {
if Current.isCatalyst {
return NotificationManagerLocalPushInterfaceDirect(delegate: self)
} else if #available(iOS 14, *) {
return NotificationManagerLocalPushInterfaceExtension()
} else {
return NotificationManagerLocalPushInterfaceDisallowed()
}
}
}()

var commandManager = NotificationCommandManager()

func setupNotifications() {
UNUserNotificationCenter.current().delegate = self

if Current.isCatalyst {
localPushManager = with(LocalPushManager()) {
$0.delegate = self
}
}
_ = localPushManager
}

func resetPushID() -> Promise<String> {
Expand Down Expand Up @@ -87,73 +84,12 @@ class NotificationManager: NSObject, LocalPushManagerDelegate {
}

private func handleRemoteNotification(userInfo: [AnyHashable: Any]) -> Guarantee<UIBackgroundFetchResult> {
let (promise, seal) = Guarantee<UIBackgroundFetchResult>.pending()

Current.Log.verbose("remote notification: \(userInfo)")

if let userInfoDict = userInfo as? [String: Any],
let hadict = userInfoDict["homeassistant"] as? [String: Any], let command = hadict["command"] as? String {
switch command {
case "request_location_update":
guard Current.settingsStore.locationSources.pushNotifications else {
Current.Log.info("ignoring request, location source of notifications is disabled")
seal(.noData)
return promise
}

Current.Log.verbose("Received remote request to provide a location update")

Current.backgroundTask(withName: "push-location-request") { remaining in
Current.api.then(on: nil) { api in
api.GetAndSendLocation(trigger: .PushNotification, maximumBackgroundTime: remaining)
}
}.map {
UIBackgroundFetchResult.newData
}.recover { _ in
Guarantee<UIBackgroundFetchResult>.value(.failed)
}.done(seal)
case "clear_badge":
Current.Log.verbose("Setting badge to 0 as requested")
UIApplication.shared.applicationIconBadgeNumber = 0
seal(.newData)
case "clear_notification":
Current.Log.verbose("clearing notification for \(userInfo)")
let keys = ["tag", "collapseId"].compactMap { hadict[$0] as? String }
if !keys.isEmpty {
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: keys)
}
seal(.newData)
case "update_complications":
firstly {
updateComplications()
}.map {
Current.Log.info("successfully updated complications from notification")
return UIBackgroundFetchResult.newData
}.recover { error in
Current.Log.error("failed to update complications from notification: \(error)")
return Guarantee<UIBackgroundFetchResult>.value(.failed)
}.done(seal)
default:
Current.Log.warning("Received unknown command via APNS! \(userInfo)")
seal(.noData)
}
} else {
seal(.noData)
}

return promise
}

func updateComplications() -> Promise<Void> {
Promise { seal in
Communicator.shared.transfer(.init(content: [:])) { result in
switch result {
case .success: seal.fulfill(())
case let .failure(error): seal.reject(error)
}
}
}.get {
NotificationCenter.default.post(name: Self.didUpdateComplicationsNotification, object: nil)
return commandManager.handle(userInfo).map {
UIBackgroundFetchResult.newData
}.recover { _ in
Guarantee<UIBackgroundFetchResult>.value(.failed)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import HAKit
import Shared

enum NotificationManagerLocalPushStatus {
case allowed(LocalPushManager.State)
case disabled
case unsupported
}

protocol NotificationManagerLocalPushInterface {
var status: NotificationManagerLocalPushStatus { get }
func addObserver(_ handler: @escaping (NotificationManagerLocalPushStatus) -> Void) -> HACancellable
}
Loading

0 comments on commit 1db6f3c

Please sign in to comment.