Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live activities initial changes #12964

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions FirebaseMessaging.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ device, and it is completely free.
base_dir = "FirebaseMessaging/"
s.source_files = [
base_dir + 'Sources/**/*',
base_dir + 'Sources/LiveActivities/*.{swift,h,m}',
base_dir + 'Sources/Protogen/nanopb/*.h',
base_dir + 'Interop/*.h',
'Interop/Analytics/Public/*.h',
Expand All @@ -46,13 +47,17 @@ device, and it is completely free.
]
s.public_header_files = base_dir + 'Sources/Public/FirebaseMessaging/*.h'
s.library = 'sqlite3'
# s.prepare_command = <<-CMD
# echo "Bridging header has been created"
# CMD
s.pod_target_xcconfig = {
'GCC_C_LANGUAGE_STANDARD' => 'c99',
'GCC_PREPROCESSOR_DEFINITIONS' =>
# for nanopb:
'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1',
# Unit tests do library imports using repo-root relative paths.
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"',
# 'SWIFT_OBJC_BRIDGING_HEADER' => '$(PODS_TARGET_SRCROOT)/FirebaseMessaging/Sources/LiveActivities/FirebaseMessaging-Bridging-Header.h',
}
s.ios.framework = 'SystemConfiguration'
s.tvos.framework = 'SystemConfiguration'
Expand Down
69 changes: 69 additions & 0 deletions FirebaseMessaging/Sources/LiveActivities/LiveActivityManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation
import ActivityKit
import UIKit

/**
Live activity manager class for FCM SDK

Functionalities:
- Keeps track of live activity updates (starting and ending)
- Keeps track of live activity token updates and push to start token updates.
- Uploads the updated tokens to FCM backend when needed.

*/
@available(iOS 16.1, *)
public class LiveActivityManager{

// To keep track of registered Live Acitvities so that they can be invalidated
private static var acitivityWrappers = [LiveActivityTypeWrapper]()

// Class to manage Live Activity tokens
private static let tokenManager:LiveActivityTokenManager = LiveActivityTokenManager.getInstance()

// Log tag for printing logs
public static let LOG_TAG = "LAM# "

public static func liveActivityRegsistration() -> RegistrationRequest{
return RegistrationRequest()
}

static func invalidateActivities(){
Task{
var refreshedIds :[String] = []
for activityWrapper in acitivityWrappers{
activityWrapper.invalidateActivities()
refreshedIds.append(contentsOf:activityWrapper.getActiveActivityIds())
}

await tokenManager.invalidateWith(activityIds: refreshedIds)

NSLog(LOG_TAG + "Invalidated")
}
}

static func setActivityWrappers(wrappers: [LiveActivityTypeWrapper]){
acitivityWrappers = wrappers
}

public static func getLiveAcitivityTokens() async -> [String:String] {
let tokens = await tokenManager.getTokens()
return tokens.mapValues { $0 };
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation
import ActivityKit

/**
Builder class to accept Live activity Registration requests
*/
@available(iOS 16.1, *)
public class RegistrationRequest{

let tokenManager:LiveActivityTokenManager = LiveActivityTokenManager.getInstance()
var acitivityDict = [String:LiveActivityTypeWrapper]()

public func add<T: ActivityAttributes>(type: T.Type) -> RegistrationRequest{
let key = String(describing: type)
acitivityDict[key] = LiveActivityTypeWrapperImpl<T>()
return self
}

/**
Registers the live activities and returns the Push to start id if supported.

PTS id is returned only if iOS 17.2 or above and atleast one Live activity type is registered with FCM
*/
public func register() -> String?{
var wrappers = [LiveActivityTypeWrapper]()
var ptsTokenId:String? = nil

if(!acitivityDict.isEmpty){
ptsTokenId = tokenManager.ptsTokenId
acitivityDict.first?.value.initPTSToken(ptsTokenId: ptsTokenId)

for wrapper in acitivityDict.values{
wrappers.append(wrapper)
wrapper.listenForActivityUpdates()
}
}

LiveActivityManager.setActivityWrappers(wrappers: wrappers)
LiveActivityManager.invalidateActivities()

return ptsTokenId
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation

/**
Token manager class which is synchronized.
*/
@available(iOS 16.1, *)
actor LiveActivityTokenManager{

private let TOKENS_DEFAULTS_KEY = "TOKENS_DEFAULTS_KEY"
private var activityTokens : [String:String]
private let userDefaults = UserDefaults(suiteName: "LAM")!
private static let shared = LiveActivityTokenManager()
public let ptsTokenId:String?

/**
Returns the singleton instance of token manager.
*/
public static func getInstance() -> LiveActivityTokenManager{
return shared
}

private init(){
self.activityTokens = userDefaults.dictionary(forKey: TOKENS_DEFAULTS_KEY) as? [String: String] ?? [:]

if #available(iOS 17.2, *) {
//Initializing PTS token id only if iOS 17.2 or above
let PTS_TOKEN_ID_DEFAULTS_KEY = "PTS_TOKEN_ID_DEFAULTS_KEY"
var ptsId = userDefaults.string(forKey: PTS_TOKEN_ID_DEFAULTS_KEY)
if(ptsId == nil || ptsId == ""){
ptsId = UUID().uuidString
userDefaults.set(ptsId, forKey: PTS_TOKEN_ID_DEFAULTS_KEY)
}
ptsTokenId = ptsId
}else{
ptsTokenId = nil
}
}

func getPTSTokenId() -> String?{
return ptsTokenId
}

func getTokens() -> [String:String]{
return activityTokens
}

func saveTokensToUserDefaults(){
userDefaults.set(activityTokens, forKey: TOKENS_DEFAULTS_KEY)
}

func haveTokenFor(activityId:String) -> Bool{
return activityTokens.keys.contains(activityId)
}

func checkAndUpdateTokenFor(activityId:String,activityToken:String){
if(activityId=="" || activityToken == ""){
return
}

if (haveTokenFor(activityId: activityId)){
let oldToken = activityTokens[activityId]

if(oldToken == nil || oldToken == "" || oldToken != activityToken){
//Token needs update
updateToken(id: activityId, token: activityToken, reason: .ActivityTokenUpdated)
}
}else{
//New token
updateToken(id: activityId, token: activityToken, reason: .ActivityTokenAdded)
}

}

func checkAndRemoveTokenFor(activityId:String){
if(activityTokens.keys.contains(activityId)){
// Remove token
let token = activityTokens[activityId]!
updateToken(id: activityId, token: token, reason: .ActivityTokenRemoved)
}
}

func invalidateWith(activityIds: [String]){
//Checking and removing ended acitivities
for acitivtyId in activityTokens.keys{
if(acitivtyId == ptsTokenId){
//PTS tokens are meant to be updated and not removed.
continue
}
if(!activityIds.contains(where: {$0==acitivtyId})){
checkAndRemoveTokenFor(activityId: acitivtyId)
}
}
}

func updateToken(id:String,token:String,reason:TokenUpdateReason){
NSLog(LiveActivityManager.LOG_TAG + "Token Update : " + String(describing: reason))
if(reason == .ActivityTokenRemoved){
activityTokens.removeValue(forKey: id)
saveTokensToUserDefaults()
// No need for FCM backend update. So returning.
return
}

activityTokens[id] = token
saveTokensToUserDefaults()

uploadToken(id: id, token: token)
}

func uploadToken(id:String,token:String){
NSLog(LiveActivityManager.LOG_TAG + "Token Upload:: Id: " + id + " token: " + token)
//TODO: Code for token upload to FCM backend.


}

/**
Reasons for token update
*/
enum TokenUpdateReason {
case ActivityTokenRemoved
case ActivityTokenAdded
case ActivityTokenUpdated
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import Foundation

/**
Protocol class invalidate and fetch details of Live activity instance fir a specific live activity type.
*/
protocol LiveActivityTypeWrapper {
/**
Invalidate the local live activity records by syncing with Activitykit apis.
*/
func invalidateActivities()
/**
Gets the list of active live activity ids
*/
func getActiveActivityIds() -> [String]
/**
Initializes PTS token
*/
func initPTSToken(ptsTokenId:String?)
/**
Listens for live activity updates
*/
func listenForActivityUpdates()
}
Loading
Loading