Merge pull request #2 from leonboe1/stable

Merge stable branch into master
This commit is contained in:
Leon Böttger authored and GitHub committed 2025-08-11 16:29:46 +02:00
commit 0abe71c809
47 files changed
+9479 -921

No files matched your search

@@ -5,7 +5,6 @@
// Created by Grishka on 08.04.2023.
//
import AppKit
import CommonCrypto
import CryptoKit
import Foundation
@@ -14,6 +13,12 @@ import System
import BigInt
import SwiftECC
#if os(macOS)
import AppKit
#else
import UIKit
#endif
class InboundNearbyConnection: NearbyConnection {
private var filesToBeReceived: [Int64: InternalFileInfo] = [:]
@@ -52,7 +57,7 @@ class InboundNearbyConnection: NearbyConnection {
override func processReceivedFrame(frameData: Data) {
if currentState != .receivingFiles {
log("[InboundNearbyConnection] Received frame in state \(currentState)...")
log("[InboundNearbyConnection \(self.id)] Received frame in state \(currentState)...")
}
do {
@@ -77,7 +82,7 @@ class InboundNearbyConnection: NearbyConnection {
do {
smsg = try Securemessage_SecureMessage(serializedBytes: frameData)
} catch {
log("[InboundNearbyConnection] Error deserializing secure message (probably due to packet filter)")
log("[InboundNearbyConnection \(self.id)] Error deserializing secure message (probably due to packet filter)")
lastError = NearbyError.packetFilterError
protocolError()
}
@@ -88,7 +93,7 @@ class InboundNearbyConnection: NearbyConnection {
}
} catch {
lastError = error
log("[InboundNearbyConnection] Error receiving frame: \(error) in state \(currentState).")
log("[InboundNearbyConnection \(self.id)] Error receiving frame: \(error) in state \(currentState).")
protocolError()
}
}
@@ -96,7 +101,9 @@ class InboundNearbyConnection: NearbyConnection {
override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type {
log("[InboundNearbyConnection] Transfer canceled")
self.lastError = NearbyError.canceled(reason: .userCanceled)
self.cancelled = true
log("[InboundNearbyConnection \(self.id)] Transfer canceled")
try sendDisconnectionAndDisconnect()
return
}
@@ -108,7 +115,15 @@ class InboundNearbyConnection: NearbyConnection {
case .receivedPairedKeyResult:
try processIntroductionFrame(frame)
default:
log("[InboundNearbyConnection] Unexpected connection state in processTransferSetupFrame: \(currentState)")
if frame.hasV1, frame.v1.hasType, frame.v1.type == .progressUpdate {
// ignore progress updates
return
}
if frame.hasV1, frame.v1.hasType, frame.v1.type == .response, case .accept = frame.v1.connectionResponse.status {
// ignore accept response frame, it is inferred since the other device set up the connection
return
}
log("[InboundNearbyConnection \(self.id)] Unexpected connection state in processTransferSetupFrame: \(currentState)")
log(frame.debugDescription)
}
}
@@ -140,7 +155,7 @@ class InboundNearbyConnection: NearbyConnection {
// only for logging
self.bytesTransferred += Int64(frame.payloadChunk.body.count)
} catch {
log("[InboundNearbyConnection] Error occurred during writing file: \(error.localizedDescription)")
log("[InboundNearbyConnection \(self.id)] Error occurred during writing file: \(error.localizedDescription)")
throw NearbyError.protocolError(error.localizedDescription)
}
@@ -148,13 +163,15 @@ class InboundNearbyConnection: NearbyConnection {
else if (frame.payloadChunk.flags & 1) == 1 {
try fileInfo.fileHandle?.close()
filesToBeReceived[id]!.fileHandle = nil
#if os(macOS)
fileInfo.progress?.unpublish()
#endif
SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL)
filesToBeReceived.removeValue(forKey: id)
if filesToBeReceived.isEmpty {
log("[InboundNearbyConnection] All files received, sending disconnection frame and disconnecting.")
log("[InboundNearbyConnection \(self.id)] All files received, sending disconnection frame and disconnecting.")
try sendDisconnectionAndDisconnect()
}
}
@@ -166,19 +183,31 @@ class InboundNearbyConnection: NearbyConnection {
if let urlStr = String(data: payload, encoding: .utf8) {
if isPlainTextTransfer {
// paste to clipboard
#if os(macOS)
// macOS clipboard
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(urlStr, forType: .string)
NearbyConnectionManager.shared.mainAppDelegate?.showCopiedToClipboardAlert()
}
else if let url = URL(string: urlStr) {
#elseif os(iOS)
// iOS clipboard
UIPasteboard.general.string = urlStr
// Optionally show an alert (requires a way to present it)
// For example, post a notification or use a delegate to show a toast or alert
#endif
} else if let url = URL(string: urlStr) {
#if os(macOS)
NSWorkspace.shared.open(url)
#elseif os(iOS)
UIApplication.shared.open(url, options: [:], completionHandler: nil)
#endif
}
}
log("[InboundNearbyConnection] Received text payload. Disconnecting...")
log("[InboundNearbyConnection \(self.id)] Received text payload. Disconnecting...")
try sendDisconnectionAndDisconnect()
return true
}
@@ -188,11 +217,13 @@ class InboundNearbyConnection: NearbyConnection {
fileInfo.progress?.completedUnitCount = filesToBeReceived[id]!.bytesTransferred
try fileInfo.fileHandle?.close()
filesToBeReceived[id]!.fileHandle = nil
#if os(macOS)
fileInfo.progress?.unpublish()
#endif
filesToBeReceived.removeValue(forKey: id)
SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL)
log("[InboundNearbyConnection] Received file payload. Disconnecting...")
log("[InboundNearbyConnection \(self.id)] Received file payload. Disconnecting...")
try sendDisconnectionAndDisconnect()
return true
}
@@ -227,7 +258,7 @@ class InboundNearbyConnection: NearbyConnection {
guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientInit ukey2message.type|data") }
guard case .clientInit = msg.messageType else {
sendUkey2Alert(type: .badMessageType)
log("[InboundNearbyConnection] Unsupported message type: \(msg.messageType)")
log("[InboundNearbyConnection \(self.id)] Unsupported message type: \(msg.messageType)")
throw NearbyError.ukey2
}
let clientInit: Securegcm_Ukey2ClientInit
@@ -235,17 +266,17 @@ class InboundNearbyConnection: NearbyConnection {
clientInit = try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData)
} catch {
sendUkey2Alert(type: .badMessageData)
log("[InboundNearbyConnection] Failed to parse clientInit: \(error)")
log("[InboundNearbyConnection \(self.id)] Failed to parse clientInit: \(error)")
throw NearbyError.ukey2
}
guard clientInit.version == 1 else {
sendUkey2Alert(type: .badVersion)
log("[InboundNearbyConnection] Unsupported clientInit version: \(clientInit.version)")
log("[InboundNearbyConnection \(self.id)] Unsupported clientInit version: \(clientInit.version)")
throw NearbyError.ukey2
}
guard clientInit.random.count == 32 else {
sendUkey2Alert(type: .badRandom)
log("[InboundNearbyConnection] Unsupported clientInit random: \(clientInit.random.count)")
log("[InboundNearbyConnection \(self.id)] Unsupported clientInit random: \(clientInit.random.count)")
throw NearbyError.ukey2
}
var found = false
@@ -258,12 +289,12 @@ class InboundNearbyConnection: NearbyConnection {
}
guard found else {
sendUkey2Alert(type: .badHandshakeCipher)
log("[InboundNearbyConnection] Unsupported clientInit handshakeCipher: \(clientInit.cipherCommitments)")
log("[InboundNearbyConnection \(self.id)] Unsupported clientInit handshakeCipher: \(clientInit.cipherCommitments)")
throw NearbyError.ukey2
}
guard clientInit.nextProtocol == "AES_256_CBC-HMAC_SHA256" else {
sendUkey2Alert(type: .badNextProtocol)
log("[InboundNearbyConnection] Unsupported clientInit nextProtocol: \(clientInit.nextProtocol)")
log("[InboundNearbyConnection \(self.id)] Unsupported clientInit nextProtocol: \(clientInit.nextProtocol)")
throw NearbyError.ukey2
}
@@ -297,14 +328,14 @@ class InboundNearbyConnection: NearbyConnection {
private func processUkey2ClientFinish(_ msg: Securegcm_Ukey2Message, raw: Data) throws {
guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientFinish ukey2message.type|data") }
guard case .clientFinish = msg.messageType else {
log("[InboundNearbyConnection] Unexpected message type \(msg.messageType)")
log("[InboundNearbyConnection \(self.id)] Unexpected message type \(msg.messageType)")
throw NearbyError.ukey2
}
var sha = SHA512()
sha.update(data: raw)
guard cipherCommitment == Data(sha.finalize()) else {
log("[InboundNearbyConnection] Invalid cipherCommitment in clientFinish")
log("[InboundNearbyConnection \(self.id)] Invalid cipherCommitment in clientFinish")
throw NearbyError.ukey2
}
@@ -345,7 +376,7 @@ class InboundNearbyConnection: NearbyConnection {
try sendTransferSetupFrame(pairedEncryption)
currentState = .sentConnectionResponse
} else {
log("[InboundNearbyConnection] Unhandled offline frame plaintext: \(frame)")
log("[InboundNearbyConnection \(self.id)] Unhandled offline frame plaintext: \(frame)")
}
}
@@ -436,7 +467,7 @@ class InboundNearbyConnection: NearbyConnection {
func rejectDueToUnsupportedFileType(_ frame: Sharing_Nearby_Frame) {
log("[InboundNearbyConnection] Rejecting transfer due to unsupported file type. Frame is \(frame.debugDescription)")
log("[InboundNearbyConnection \(self.id)] Rejecting transfer due to unsupported file type. Frame is \(frame.debugDescription)")
NearbyConnectionManager.shared.mainAppDelegate?.showUnsupportedFileAlert(for: remoteDeviceInfo)
rejectTransfer(with: .unsupportedAttachmentType)
@@ -456,7 +487,7 @@ class InboundNearbyConnection: NearbyConnection {
private func acceptTransfer(storeInTemp: Bool) {
if currentState == .disconnected {
log("[InboundNearbyConnection] Detected timeout, not accepting transfer")
log("[InboundNearbyConnection \(self.id)] Detected timeout, not accepting transfer")
return
}
@@ -477,9 +508,13 @@ class InboundNearbyConnection: NearbyConnection {
progress.totalUnitCount = file.meta.size
progress.kind = .file
progress.isPausable = false
#if os(macOS)
progress.publish()
#endif
filesToBeReceived[id]!.progress = progress
filesToBeReceived[id]!.created = true
log("[InboundNearbyConnection \(self.id)] Accepted file with size \(file.meta.size)")
}
var frame = Sharing_Nearby_Frame()
@@ -487,6 +522,7 @@ class InboundNearbyConnection: NearbyConnection {
frame.v1.type = .response
frame.v1.connectionResponse.status = .accept
currentState = .receivingFiles
isTransferring = true
try sendTransferSetupFrame(frame)
} catch {
lastError = error
@@ -499,7 +535,7 @@ class InboundNearbyConnection: NearbyConnection {
self.wasRejected = true
log("[InboundNearbyConnection] Rejecting transfer because of \( reason)")
log("[InboundNearbyConnection \(self.id)] Rejecting transfer because of \( reason)")
var frame = Sharing_Nearby_Frame()
frame.version = .v1
@@ -509,7 +545,7 @@ class InboundNearbyConnection: NearbyConnection {
try sendTransferSetupFrame(frame)
try sendDisconnectionAndDisconnect()
} catch {
log("[InboundNearbyConnection] Error \(error)")
log("[InboundNearbyConnection \(self.id)] Error \(error)")
protocolError()
}
}
@@ -518,9 +554,11 @@ class InboundNearbyConnection: NearbyConnection {
private func deletePartiallyReceivedFiles() {
for (_, file) in filesToBeReceived {
#if os(macOS)
if let progress = file.progress {
progress.unpublish()
}
#endif
guard file.created else { continue }
do {
+100 -35
View File
@@ -10,7 +10,6 @@ import CryptoKit
import Foundation
import Network
import System
import BigInt
import SwiftECC
@@ -26,12 +25,19 @@ class NearbyConnection {
var encryptionDone: Bool = false
var lastError: Error?
var bytesTransferred: Int64 = 0
var cancelled: Bool = false
var isTransferring: Bool = false {
didSet {
log("[NearbyConnection \(self.id)] Now transferring. Setting up data transfer inactivity timer.")
startDataTransferTimer(previousBytesTransferred: bytesTransferred)
}
}
private var payloadBuffers: [Int64: NSMutableData] = [:]
private var connectionClosed: Bool = false
private var inactivityTimer: DispatchSourceTimer?
private let timeoutInterval: TimeInterval = 50
private let timeoutInterval: TimeInterval = 30
// UKEY2-related state
var publicKey: ECPublicKey?
@@ -59,6 +65,9 @@ class NearbyConnection {
func start() {
log("[NearbyConnection \(self.id)] Starting connection.")
connection.stateUpdateHandler = { state in
if !self.connectionClosed {
@@ -69,12 +78,10 @@ class NearbyConnection {
// If connection reset by peer, it could still be a valid file transfer that has not been processed yet. => Wait
if err == .posix(.ECONNRESET) {
NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) {
// If already closed, ignore the error, as file transfer has been processed
if !self.connectionClosed {
recordErrorAndDisconnect(err: err)
}
log("[NearbyConnection \(self.id)] Network connection reset error detected.")
self.connectionClosedByPeer {
recordErrorAndDisconnect(err: err)
}
}
else {
@@ -83,7 +90,7 @@ class NearbyConnection {
}
}
else {
log("[NearbyConnection] Connection already closed, ignoring state update: \(state)")
log("[NearbyConnection \(self.id)] Connection already closed, ignoring state update: \(state)")
}
}
@@ -91,16 +98,16 @@ class NearbyConnection {
func recordErrorAndDisconnect(err: NWError) {
self.lastError = err
log("[NearbyConnection] Connection Error: \(err)")
log("[NearbyConnection \(self.id)] Connection Error: \(err)")
// If the error is a connection reset, it could be due to firewall issues.
if err == .posix(.ENOTCONN) {
ConnectionFailureTracker.shared.recordFailure {
NearbyConnectionManager.shared.mainAppDelegate?.showFirewallAlert()
}
log("[NearbyConnection \(self.id)] Network not connected error detected (firewall likely).")
self.lastError = NearbyError.firewallError
}
if err == .posix(.ENETDOWN) {
log("[NearbyConnection \(self.id)] Network down error detected.")
self.lastError = NearbyError.protocolError("Error.NetworkDown".localized())
}
@@ -109,11 +116,22 @@ class NearbyConnection {
}
func connectionClosedByPeer(onError: @escaping () -> Void) {
NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) {
// If already closed, ignore the error, as file transfer has been processed
if !self.connectionClosed {
onError()
}
}
}
func connectionReady() {}
func protocolError() {
log("[NearbyConnection] Protocol error: \(String(describing: lastError))")
log("[NearbyConnection \(self.id)] Protocol error: \(String(describing: lastError))")
disconnect()
}
@@ -149,17 +167,21 @@ class NearbyConnection {
return
}
if isComplete {
log("[NearbyConnection] Connection closed by peer during receiveFrameAsync")
self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized())
self.disconnect()
log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync()")
self.connectionClosedByPeer {
self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized())
self.disconnect()
}
return
}
if !(error == nil) {
log("[NearbyConnection \(self.id)] Error during receiveFrameAsync(): \(String(describing: error))")
self.lastError = error
self.protocolError()
return
}
guard let content = content else {
log("[NearbyConnection \(self.id)] Received nil content during receiveFrameAsync(). IsComplete: \(isComplete)")
assertionFailure()
return
}
@@ -170,29 +192,42 @@ class NearbyConnection {
return
}
self.startInactivityTimer()
self.startAndResetHeartbeatTimer()
self.receiveFrameAsync(length: frameLength)
}
}
private func receiveFrameAsync(length: UInt32) {
connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, _, isComplete, _ in
connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { [self] content, _, isComplete, _ in
if self.connectionClosed {
return
}
if isComplete {
log("[NearbyConnection] Connection closed by peer during receiveFrameAsync")
self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized())
self.disconnect()
log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync(length:)")
if let content = content, !content.isEmpty {
log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync(length:), but trying to process the frame anyway. Frame length: \(content.count)")
self.processReceivedFrame(frameData: content)
}
else {
log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync(length:), but no content received anymore (\(String(describing: content))).")
}
self.connectionClosedByPeer {
self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized())
self.disconnect()
}
return
}
guard let content = content else {
log("[NearbyConnection \(self.id)] Received nil content during receiveFrameAsync(length:). IsComplete: \(isComplete)")
self.protocolError()
return
}
self.startInactivityTimer()
self.startAndResetHeartbeatTimer()
self.processReceivedFrame(frameData: content)
self.receiveFrameAsync()
}
@@ -263,7 +298,7 @@ class NearbyConnection {
func sendTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
log("[NearbyConnection] Sending transfer setup frame.")
log("[NearbyConnection \(self.id)] Sending transfer setup frame.")
try sendBytesPayload(data: frame.serializedData(), id: Int64.random(in: Int64.min ... Int64.max))
}
@@ -299,7 +334,7 @@ class NearbyConnection {
guard smsg.hasSignature, smsg.hasHeaderAndBody else { throw NearbyError.requiredFieldMissing("secureMessage.signature|headerAndBody") }
let hmac = Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: recvHmacKey!))
guard hmac == smsg.signature else { throw NearbyError.protocolError("hmac!=signature: Expected \(hmac), got \(smsg.signature)") }
guard hmac == smsg.signature else { throw NearbyError.protocolError("Error.Signature".localized()) }
let headerAndBody = try Securemessage_HeaderAndBody(serializedBytes: smsg.headerAndBody)
var decryptedData = Data(count: headerAndBody.body.count)
@@ -338,8 +373,14 @@ class NearbyConnection {
guard header.hasType, header.hasID else { throw NearbyError.requiredFieldMissing("payloadHeader.type|id") }
guard payloadTransfer.hasPayloadChunk, chunk.hasOffset, chunk.hasFlags else {
log("[NearbyConnection] Payload transfer chunk is missing offset or flags, likely canceled.")
throw NearbyError.canceled(reason: .userCanceled)
if payloadTransfer.controlMessage.event == .payloadCanceled {
log("[NearbyConnection \(self.id)] Cancel control frame received.")
throw NearbyError.canceled(reason: .userCanceled)
}
log("[NearbyConnection \(self.id)] Payload transfer chunk is missing offset or flags. Frame is \(payloadTransfer.debugDescription)")
throw NearbyError.requiredFieldMissing("payloadChunk.offset|flags")
}
if case .bytes = header.type {
@@ -383,11 +424,16 @@ class NearbyConnection {
}
else if offlineFrame.hasV1, offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type {
log("[NearbyConnection] Sent keep-alive, \(self.bytesTransferred) bytes sent")
log("[NearbyConnection \(self.id)] Sent keep-alive, \(self.bytesTransferred) bytes sent")
sendKeepAlive(ack: true)
} else {
log("[NearbyConnection] Unhandled offline frame encrypted: \(offlineFrame)")
if offlineFrame.hasV1, offlineFrame.v1.hasType, offlineFrame.v1.type == .bandwidthUpgradeRetry {
// only supporting WiFi for now, ignore upgrade request to other mediums
return
}
log("[NearbyConnection \(self.id)] Unhandled offline frame encrypted: \(offlineFrame)")
}
}
@@ -491,7 +537,7 @@ class NearbyConnection {
func disconnect() {
log("[NearbyConnection] Disconnecting from connection.")
log("[NearbyConnection \(self.id)] Disconnecting from connection.")
connection.stateUpdateHandler = nil
inactivityTimer?.cancel()
@@ -513,7 +559,7 @@ class NearbyConnection {
try sendFrameAsync(offlineFrame.serializedData())
}
log("[NearbyConnection] Sent disconnection frame during sendDisconnectionAndDisconnect")
log("[NearbyConnection \(self.id)] Sent disconnection frame during sendDisconnectionAndDisconnect")
disconnect()
}
@@ -526,7 +572,7 @@ class NearbyConnection {
msg.messageData = try! alert.serializedData()
sendFrameAsync(try! msg.serializedData())
log("[NearbyConnection] Sent UKEY2 alert: \(type)")
log("[NearbyConnection \(self.id)] Sent UKEY2 alert: \(type)")
disconnect()
}
@@ -544,12 +590,12 @@ class NearbyConnection {
try sendFrameAsync(offlineFrame.serializedData())
}
} catch {
log("[NearbyConnection] Error sending KEEP_ALIVE: \(error)")
log("[NearbyConnection \(self.id)] Error sending KEEP_ALIVE: \(error)")
}
}
func startInactivityTimer() {
func startAndResetHeartbeatTimer() {
// Cancel previous timer if any
inactivityTimer?.cancel()
@@ -561,7 +607,7 @@ class NearbyConnection {
guard let self = self else { return }
if !self.connectionClosed {
log("[NearbyConnection] Connection timeout: No message received in \(self.timeoutInterval) seconds")
log("[NearbyConnection \(self.id)] Connection timeout: No message received in \(self.timeoutInterval) seconds")
self.lastError = NearbyError.canceled(reason: .timedOut)
self.disconnect()
}
@@ -571,6 +617,25 @@ class NearbyConnection {
}
func startDataTransferTimer(previousBytesTransferred: Int64) {
NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + timeoutInterval) {
if previousBytesTransferred < self.bytesTransferred {
// everything good, transferred more than last check, schedule next check
self.startDataTransferTimer(previousBytesTransferred: self.bytesTransferred)
}
else {
// connection stale, need to abort
if !self.connectionClosed {
log("[NearbyConnection \(self.id)] Connection timeout: No more data received in \(self.timeoutInterval) seconds")
self.lastError = NearbyError.canceled(reason: .timedOut)
self.disconnect()
}
}
}
}
// -- MARK: - Internal Data Model
struct InternalFileInfo {
@@ -9,6 +9,9 @@ import Foundation
import Network
import System
#if os(iOS)
import UIKit
#endif
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate {
@@ -90,20 +93,29 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
]
let name = Data(nameBytes).urlSafeBase64EncodedString()
let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
let port = Int32(tcpListener.port!.rawValue)
mdnsServices = serviceTypes.map { serviceType in
let service = NetService(domain: "", type: serviceType, name: name, port: port)
service.delegate = self
service.setTXTRecord(NetService.data(fromTXTRecord: [
"n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
"n": getEndpointInfo().serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
]))
service.publish()
return service
}
}
func getEndpointInfo() -> EndpointInfo {
#if os(macOS)
let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
#else
let endpointInfo = EndpointInfo(name: UIDevice.current.name, deviceType: .phone)
#endif
return endpointInfo
}
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection _: InboundNearbyConnection) {
@@ -123,7 +135,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
public func submitUserConsent(transferID: String, accept: Bool, storeInTemp: Bool = false) {
guard let conn = incomingConnections[transferID] else { return }
log("[NearbyConnectionManager] Submitting user consent for transfer, accepted: \(accept), store in temp: \(storeInTemp)")
log("[NearbyConnectionManager] Submitting user consent for transfer \(transferID), accepted: \(accept), store in temp: \(storeInTemp)")
conn.submitUserConsent(accepted: accept, storeInTemp: storeInTemp)
}
@@ -24,7 +24,6 @@ class OutboundNearbyConnection: NearbyConnection {
private var currentTransfer: OutgoingFileTransfer?
private var totalBytesToSend: Int64 = 0
private var totalBytesSent: Int64 = 0
private var cancelled: Bool = false
private var textPayloadID: Int64 = 0
public var delegate: OutboundNearbyConnectionDelegate?
@@ -102,7 +101,7 @@ class OutboundNearbyConnection: NearbyConnection {
override func processReceivedFrame(frameData: Data) {
if currentState != .sendingFiles {
log("[OutboundNearbyConnection] Received frame in state \(currentState)...")
log("[OutboundNearbyConnection \(self.id)] Received frame in state \(currentState)...")
}
do {
@@ -119,7 +118,7 @@ class OutboundNearbyConnection: NearbyConnection {
}
} catch {
log("[OutboundNearbyConnection] Error occured while processing frame with data \(frameData.hex): \(error)")
log("[OutboundNearbyConnection \(self.id)] Error occured while processing frame with data \(frameData.hex): \(error)")
if case NearbyError.ukey2 = error {
// do nothing
@@ -136,10 +135,11 @@ class OutboundNearbyConnection: NearbyConnection {
override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type {
log("[OutboundNearbyConnection] Transfer canceled")
self.cancelled = true
self.lastError = NearbyError.canceled(reason: .userCanceled)
log("[OutboundNearbyConnection \(self.id)] Transfer canceled")
try sendDisconnectionAndDisconnect()
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled))
delegate?.outboundConnection(connection: self, failedWithError: self.lastError!)
return
}
@@ -153,7 +153,7 @@ class OutboundNearbyConnection: NearbyConnection {
case .sendingFiles:
break
default:
assertionFailure("[OutboundNearbyConnection] Unexpected state \(currentState)")
assertionFailure("[OutboundNearbyConnection \(self.id)] Unexpected state \(currentState)")
}
}
@@ -165,14 +165,16 @@ class OutboundNearbyConnection: NearbyConnection {
private func sendConnectionRequest() throws {
let endpointInfo = NearbyConnectionManager.shared.getEndpointInfo()
var frame = Location_Nearby_Connections_OfflineFrame()
frame.version = .v1
frame.v1 = Location_Nearby_Connections_V1Frame()
frame.v1.type = .connectionRequest
frame.v1.connectionRequest = Location_Nearby_Connections_ConnectionRequestFrame()
frame.v1.connectionRequest.endpointID = String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)!
frame.v1.connectionRequest.endpointName = Host.current().localizedName!
let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
frame.v1.connectionRequest.endpointName = endpointInfo.name
frame.v1.connectionRequest.endpointInfo = endpointInfo.serialize()
frame.v1.connectionRequest.mediums = [.wifiLan]
try sendFrameAsync(frame.serializedData())
@@ -222,23 +224,23 @@ class OutboundNearbyConnection: NearbyConnection {
ukeyServerInitMsgData = raw
guard frame.messageType == .serverInit else {
sendUkey2Alert(type: .badMessageType)
log("[OutboundNearbyConnection] Invalid message type: \(frame.messageType)")
log("[OutboundNearbyConnection \(self.id)] Invalid message type: \(frame.messageType)")
throw NearbyError.ukey2
}
let serverInit = try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData)
guard serverInit.version == 1 else {
sendUkey2Alert(type: .badVersion)
log("[OutboundNearbyConnection] Invalid version: \(serverInit.version)")
log("[OutboundNearbyConnection \(self.id)] Invalid version: \(serverInit.version)")
throw NearbyError.ukey2
}
guard serverInit.random.count == 32 else {
sendUkey2Alert(type: .badRandom)
log("[OutboundNearbyConnection] Invalid random: \(serverInit.random.count)")
log("[OutboundNearbyConnection \(self.id)] Invalid random: \(serverInit.random.count)")
throw NearbyError.ukey2
}
guard serverInit.handshakeCipher == .p256Sha512 else {
sendUkey2Alert(type: .badHandshakeCipher)
log("[OutboundNearbyConnection] Invalid handshake cipher: \(serverInit.handshakeCipher)")
log("[OutboundNearbyConnection \(self.id)] Invalid handshake cipher: \(serverInit.handshakeCipher)")
throw NearbyError.ukey2
}
@@ -338,7 +340,7 @@ class OutboundNearbyConnection: NearbyConnection {
} else if meta.mimeType.starts(with: "audio/") {
meta.type = .audio
} else if url.pathExtension.lowercased() == "apk" {
meta.type = .app
meta.type = .androidApp
} else {
meta.type = .unknown
}
@@ -346,9 +348,10 @@ class OutboundNearbyConnection: NearbyConnection {
try queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0))
introduction.v1.introduction.fileMetadata.append(meta)
totalBytesToSend += meta.size
log("[OutboundNearbyConnection \(self.id)] Sending file with \(meta.size) bytes")
}
}
log("[OutboundNearbyConnection] Sent introduction: \(introduction)")
try sendTransferSetupFrame(introduction)
currentState = .sentIntroduction
@@ -360,6 +363,7 @@ class OutboundNearbyConnection: NearbyConnection {
switch frame.v1.connectionResponse.status {
case .accept:
currentState = .sendingFiles
isTransferring = true
delegate?.outboundConnectionTransferAccepted(connection: self)
if let textToSend = textToSend {
@@ -412,7 +416,7 @@ class OutboundNearbyConnection: NearbyConnection {
try currentTransfer?.handle?.close()
}
if queue.isEmpty {
log("[OutboundNearbyConnection] Disconnecting because all files have been transferred")
log("[OutboundNearbyConnection \(self.id)] Disconnecting because all files have been transferred")
try sendDisconnectionAndDisconnect()
delegate?.outboundConnectionTransferFinished(connection: self)
return
@@ -455,7 +459,7 @@ class OutboundNearbyConnection: NearbyConnection {
// only for logging
self.bytesTransferred += Int64(fileBuffer.count)
startInactivityTimer()
startAndResetHeartbeatTimer()
delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent) / Double(totalBytesToSend))
if currentTransfer!.currentOffset == currentTransfer!.totalBytes {
@@ -475,7 +479,7 @@ class OutboundNearbyConnection: NearbyConnection {
wrapper.v1.type = .payloadTransfer
wrapper.v1.payloadTransfer = transfer
try encryptAndSendOfflineFrame(wrapper)
log("[OutboundNearbyConnection] Sent EOF, current transfer: \(String(describing: currentTransfer))")
log("[OutboundNearbyConnection \(self.id)] Sent EOF, current transfer: \(String(describing: currentTransfer))")
}
}
@@ -27,7 +27,6 @@ protocol OutboundNearbyConnectionDelegate {
public protocol MainAppDelegate {
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo)
func incomingTransfer(id: String, didFinishWith error: Error?)
func showFirewallAlert()
func showCopiedToClipboardAlert()
func showUnsupportedFileAlert(for: RemoteDeviceInfo?)
}
@@ -44,7 +43,7 @@ public protocol ShareExtensionDelegate: AnyObject {
}
public struct RemoteDeviceInfo {
public struct RemoteDeviceInfo: Identifiable, Equatable {
public let name: String
public let type: DeviceType
public var id: String?
@@ -164,6 +163,7 @@ public enum NearbyError: Error {
case requiredFieldMissing(_ message: String)
case ukey2
case packetFilterError
case firewallError
case inputOutput
case canceled(reason: CancellationReason)
File diff suppressed because it is too large. Load diff
File renamed without changes.
@@ -16,6 +16,8 @@ syntax = "proto2";
package location.nearby.connections;
// import "storage/datapol/annotations/proto/semantic_annotations.proto";
option optimize_for = LITE_RUNTIME;
option java_outer_classname = "OfflineWireFormatsProto";
option java_package = "com.google.location.nearby.connections.proto";
@@ -43,6 +45,11 @@ message V1Frame {
KEEP_ALIVE = 5;
DISCONNECTION = 6;
PAIRED_KEY_ENCRYPTION = 7;
AUTHENTICATION_MESSAGE = 8;
AUTHENTICATION_RESULT = 9;
AUTO_RESUME = 10;
AUTO_RECONNECT = 11;
BANDWIDTH_UPGRADE_RETRY = 12;
}
optional FrameType type = 1;
@@ -54,6 +61,11 @@ message V1Frame {
optional KeepAliveFrame keep_alive = 6;
optional DisconnectionFrame disconnection = 7;
optional PairedKeyEncryptionFrame paired_key_encryption = 8;
optional AuthenticationMessageFrame authentication_message = 9;
optional AuthenticationResultFrame authentication_result = 10;
optional AutoResumeFrame auto_resume = 11;
optional AutoReconnectFrame auto_reconnect = 12;
optional BandwidthUpgradeRetryFrame bandwidth_upgrade_retry = 13;
}
message ConnectionRequestFrame {
@@ -72,6 +84,15 @@ message ConnectionRequestFrame {
WEB_RTC = 9;
BLE_L2CAP = 10;
USB = 11;
WEB_RTC_NON_CELLULAR = 12;
AWDL = 13;
}
// LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto)
// LINT.IfChange
enum ConnectionMode {
LEGACY = 0;
INSTANT = 1;
}
// LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto)
@@ -91,9 +112,16 @@ message ConnectionRequestFrame {
optional int32 keep_alive_interval_millis = 8;
optional int32 keep_alive_timeout_millis = 9;
// The type of {@link Device} object.
optional int32 device_type = 10 [default = 0];
optional int32 device_type = 10 [default = 0, deprecated = true];
// The bytes of serialized {@link Device} object.
optional bytes device_info = 11;
optional bytes device_info = 11 [deprecated = true];
// Represents the {@link Device} that invokes the request.
oneof Device {
ConnectionsDevice connections_device = 12;
PresenceDevice presence_device = 13;
}
optional ConnectionMode connection_mode = 14;
optional LocationHint location_hint = 15;
}
message ConnectionResponseFrame {
@@ -125,7 +153,10 @@ message ConnectionResponseFrame {
// means bt supports multiplex while 0x00 means not. Refer to ClientProxy.java
// for the bit usages.
optional int32 multiplex_socket_bitmask = 5;
optional int32 nearby_connections_version = 6;
optional int32 nearby_connections_version = 6 [deprecated = true];
optional int32 safe_to_disconnect_version = 7;
optional LocationHint location_hint = 8;
optional int32 keep_alive_timeout_millis = 9;
}
message PayloadTransferFrame {
@@ -133,6 +164,7 @@ message PayloadTransferFrame {
UNKNOWN_PACKET_TYPE = 0;
DATA = 1;
CONTROL = 2;
PAYLOAD_ACK = 3;
}
message PayloadHeader {
@@ -148,6 +180,8 @@ message PayloadTransferFrame {
optional bool is_sensitive = 4;
optional string file_name = 5;
optional string parent_folder = 6;
// Time since the epoch in milliseconds.
optional int64 last_modified_timestamp_millis = 7;
}
// Accompanies DATA packets.
@@ -158,6 +192,7 @@ message PayloadTransferFrame {
optional int32 flags = 1;
optional int64 offset = 2;
optional bytes body = 3;
optional int32 index = 4;
}
// Accompanies CONTROL packets.
@@ -166,7 +201,8 @@ message PayloadTransferFrame {
UNKNOWN_EVENT_TYPE = 0;
PAYLOAD_ERROR = 1;
PAYLOAD_CANCELED = 2;
PAYLOAD_RECEIVED_ACK = 3;
// Use PacketType.PAYLOAD_ACK instead
PAYLOAD_RECEIVED_ACK = 3 [deprecated = true];
}
optional EventType event = 1;
@@ -190,6 +226,9 @@ message BandwidthUpgradeNegotiationFrame {
CLIENT_INTRODUCTION = 4;
UPGRADE_FAILURE = 5;
CLIENT_INTRODUCTION_ACK = 6;
// The event type that requires the remote device to send the available
// upgrade path.
UPGRADE_PATH_REQUEST = 7;
}
// Accompanies UPGRADE_PATH_AVAILABLE and UPGRADE_FAILURE events.
@@ -208,12 +247,15 @@ message BandwidthUpgradeNegotiationFrame {
WEB_RTC = 9;
// 10 is reserved.
USB = 11;
WEB_RTC_NON_CELLULAR = 12;
AWDL = 13;
}
// Accompanies Medium.WIFI_HOTSPOT.
message WifiHotspotCredentials {
optional string ssid = 1;
optional string password = 2;
optional string password = 2
/* type = ST_ACCOUNT_CREDENTIAL */;
optional int32 port = 3;
optional string gateway = 4 [default = "0.0.0.0"];
// This field can be a band or frequency
@@ -236,16 +278,22 @@ message BandwidthUpgradeNegotiationFrame {
message WifiAwareCredentials {
optional string service_id = 1;
optional bytes service_info = 2;
optional string password = 3;
optional string password = 3
/* type = ST_ACCOUNT_CREDENTIAL */;
}
// Accompanies Medium.WIFI_DIRECT.
message WifiDirectCredentials {
optional string ssid = 1;
optional string password = 2;
optional string password = 2
/* type = ST_ACCOUNT_CREDENTIAL */;
optional int32 port = 3;
optional int32 frequency = 4;
optional string gateway = 5 [default = "0.0.0.0"];
// IPv6 link-local address, network order (128bits).
// The GO should listen on both IPv4 and IPv6 addresses.
// https://en.wikipedia.org/wiki/Link-local_address#IPv6
optional bytes ip_v6_address = 6;
}
// Accompanies Medium.WEB_RTC
@@ -254,6 +302,20 @@ message BandwidthUpgradeNegotiationFrame {
optional LocationHint location_hint = 2;
}
// Accompanies Medium.AWDL.
message AwdlCredentials {
optional string service_name = 1;
optional string service_type = 2;
optional string password = 3
/* type = ST_ACCOUNT_CREDENTIAL */;
}
message UpgradePathRequest {
// Supported mediums on the advertiser device.
repeated Medium mediums = 1 [packed = true];
optional MediumMetadata medium_meta_data = 2;
}
optional Medium medium = 1;
// Exactly one of the following fields will be set.
@@ -263,18 +325,27 @@ message BandwidthUpgradeNegotiationFrame {
optional WifiAwareCredentials wifi_aware_credentials = 5;
optional WifiDirectCredentials wifi_direct_credentials = 6;
optional WebRtcCredentials web_rtc_credentials = 8;
optional AwdlCredentials awdl_credentials = 11;
// Disable Encryption for this upgrade medium to improve throughput.
optional bool supports_disabling_encryption = 7;
// An ack will be sent after the CLIENT_INTRODUCTION frame.
optional bool supports_client_introduction_ack = 9;
optional UpgradePathRequest upgrade_path_request = 10;
}
// Accompanies SAFE_TO_CLOSE_PRIOR_CHANNEL events.
message SafeToClosePriorChannel {
optional int32 sta_frequency = 1;
}
// Accompanies CLIENT_INTRODUCTION events.
message ClientIntroduction {
optional string endpoint_id = 1;
optional bool supports_disabling_encryption = 2;
optional string last_endpoint_id = 3;
}
// Accompanies CLIENT_INTRODUCTION_ACK events.
@@ -286,11 +357,44 @@ message BandwidthUpgradeNegotiationFrame {
optional UpgradePathInfo upgrade_path_info = 2;
optional ClientIntroduction client_introduction = 3;
optional ClientIntroductionAck client_introduction_ack = 4;
optional SafeToClosePriorChannel safe_to_close_prior_channel = 5;
}
message BandwidthUpgradeRetryFrame {
// Should always match cs/symbol:location.nearby.proto.connections.Medium
// LINT.IfChange
enum Medium {
UNKNOWN_MEDIUM = 0;
// 1 is reserved.
BLUETOOTH = 2;
WIFI_HOTSPOT = 3;
BLE = 4;
WIFI_LAN = 5;
WIFI_AWARE = 6;
NFC = 7;
WIFI_DIRECT = 8;
WEB_RTC = 9;
BLE_L2CAP = 10;
USB = 11;
WEB_RTC_NON_CELLULAR = 12;
AWDL = 13;
}
// LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto)
// The mediums this device supports upgrading to. This list should be filtered
// by both the strategy and this device's individual limitations.
repeated Medium supported_medium = 1;
// If true, expect the remote endpoint to send back the latest
// supported_medium.
optional bool is_request = 2;
}
message KeepAliveFrame {
// And ack will be sent after receiving KEEP_ALIVE frame.
optional bool ack = 1;
// The sequence number
optional uint32 seq_num = 2;
}
// Informs the remote side to immediately severe the socket connection.
@@ -313,6 +417,45 @@ message PairedKeyEncryptionFrame {
optional bytes signed_data = 1;
}
// Nearby Connections authentication frame, contains the bytes format of a
// DeviceProvider's authentication message.
message AuthenticationMessageFrame {
// An auth message generated by DeviceProvider.
// To be sent to the remote device for verification during connection setups.
optional bytes auth_message = 1;
}
// Nearby Connections authentication result frame.
message AuthenticationResultFrame {
// The authentication result. Non null if this frame is used to exchange
// authentication result.
optional int32 result = 1;
}
message AutoResumeFrame {
enum EventType {
UNKNOWN_AUTO_RESUME_EVENT_TYPE = 0;
PAYLOAD_RESUME_TRANSFER_START = 1;
PAYLOAD_RESUME_TRANSFER_ACK = 2;
}
optional EventType event_type = 1;
optional int64 pending_payload_id = 2;
optional int32 next_payload_chunk_index = 3;
optional int32 version = 4;
}
message AutoReconnectFrame {
enum EventType {
UNKNOWN_EVENT_TYPE = 0;
CLIENT_INTRODUCTION = 1;
CLIENT_INTRODUCTION_ACK = 2;
}
optional string endpoint_id = 1;
optional EventType event_type = 2;
optional string last_endpoint_id = 3;
}
message MediumMetadata {
// True if local device supports 5GHz.
optional bool supports_5_ghz = 1;
@@ -338,6 +481,8 @@ message MediumMetadata {
optional WifiAwareUsableChannels wifi_aware_usable_channels = 10;
// Usable WiFi Hotspot STA channels on the local device.
optional WifiHotspotStaUsableChannels wifi_hotspot_sta_usable_channels = 11;
// The supported medium roles.
optional MediumRole medium_role = 12;
}
// Available channels on the local device.
@@ -365,6 +510,18 @@ message WifiHotspotStaUsableChannels {
repeated int32 channels = 1 [packed = true];
}
// The medium roles.
message MediumRole {
optional bool support_wifi_direct_group_owner = 1;
optional bool support_wifi_direct_group_client = 2;
optional bool support_wifi_hotspot_host = 3;
optional bool support_wifi_hotspot_client = 4;
optional bool support_wifi_aware_publisher = 5;
optional bool support_wifi_aware_subscriber = 6;
optional bool support_awdl_publisher = 7;
optional bool support_awdl_subscriber = 8;
}
// LocationHint is used to specify a location as well as format.
message LocationHint {
// Location is the location, provided in the format specified by format.
@@ -401,3 +558,39 @@ message OsInfo {
optional OsType type = 1;
}
enum EndpointType {
UNKNOWN_ENDPOINT = 0;
CONNECTIONS_ENDPOINT = 1;
PRESENCE_ENDPOINT = 2;
}
message ConnectionsDevice {
optional string endpoint_id = 1;
optional EndpointType endpoint_type = 2;
optional bytes connectivity_info_list = 3; // Data Elements.
optional bytes endpoint_info = 4;
}
message PresenceDevice {
enum DeviceType {
UNKNOWN = 0;
PHONE = 1;
TABLET = 2;
DISPLAY = 3;
LAPTOP = 4;
TV = 5;
WATCH = 6;
}
optional string endpoint_id = 1;
optional EndpointType endpoint_type = 2;
optional bytes connectivity_info_list = 3; // Data Elements.
optional int64 device_id = 4;
optional string device_name = 5;
optional DeviceType device_type = 6;
optional string device_image_url = 7;
repeated ConnectionRequestFrame.Medium discovery_medium = 8 [packed = true];
repeated int32 actions = 9 [packed = true];
repeated int64 identity_type = 10 [packed = true];
}
@@ -0,0 +1,962 @@
// Copyright 2020 Google LLC
//
// 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
//
// https://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.
syntax = "proto2";
package location.nearby.proto.sharing;
// import "logs/proto/logs_annotations/logs_annotations.proto";
// import "logs/proto/wireless/beto/beto_enums.proto";
// import "logs/proto/wireless/beto/log_dimension_annotations.proto";
// option (logs_proto.file_not_used_for_logging_except_enums) = true;
option optimize_for = LITE_RUNTIME;
option java_package = "com.google.location.nearby.proto";
option java_outer_classname = "SharingEnums";
option objc_class_prefix = "GNSHP";
// We use event based logging (an event object can be constructed and logged
// immediately when they occur). To obtain session based information (e.g.
// durations, counting incoming introductions), we use flowId (sender/receiver)
// in NearbyClearcutLogger (for android, or clearcut_event_logger as the
// equivalence for Windows) for all events (may exclude settings), and
// session_id for a pair of events (start and end of a session).
// Next id: 74
enum EventType {
UNKNOWN_EVENT_TYPE = 0;
// LINT.IfChange(EventType)
// When new users accept agreements (like grant permission to contacts for
// CONTACT_ONLY visibility) and are enrolled into Nearby Sharing. This event
// is used to count number of new users.
ACCEPT_AGREEMENTS = 1;
// User enables/disables nearby sharing from setting or tile service.
ENABLE_NEARBY_SHARING = 2;
// User sets visibility preference from setting.
SET_VISIBILITY = 3;
// Describe attachments immediately when Nearby Sharing is opened by another
// app which is used to generate/attach attachments to be shared with other
// devices.
DESCRIBE_ATTACHMENTS = 4
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// Start of a scanning phase at sender.
SCAN_FOR_SHARE_TARGETS_START = 5 /*[[*/
/*( device_role = DEVICE_ROLE_INITIATOR, )*/
/*( stage = STAGE_QS_DISCOVER_START )*/
/*]]*/;
// End of the scanning phase at sender.
SCAN_FOR_SHARE_TARGETS_END = 6 /*[[*/
/*( device_role = DEVICE_ROLE_INITIATOR, )*/
/*( stage = STAGE_QS_DISCOVER_END )*/
/*]]*/;
// Receiver advertises itself for presence (a pseudo session).
ADVERTISE_DEVICE_PRESENCE_START = 7 /*[[*/
/*( device_role = DEVICE_ROLE_REMOTE, )*/
/*( stage = STAGE_QS_ADVERTISE_START )*/
/*]]*/;
// End of the advertising phase at receiver.
ADVERTISE_DEVICE_PRESENCE_END = 8 /*[[*/
/*( device_role = DEVICE_ROLE_REMOTE, )*/
/*( stage = STAGE_QS_ADVERTISE_END )*/
/*]]*/;
// Sender sends a fast initialization to receiver.
SEND_FAST_INITIALIZATION = 9
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// Receiver receives the fast initialization.
RECEIVE_FAST_INITIALIZATION = 10
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Sender discovers a share target.
DISCOVER_SHARE_TARGET = 11 /*[[*/
/*( device_role = DEVICE_ROLE_INITIATOR, )*/
/*( stage = STAGE_QS_DISCOVERED )*/
/*]]*/;
// Sender sends introduction (before attachments being sent).
SEND_INTRODUCTION = 12
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// Receiver receives introduction.
RECEIVE_INTRODUCTION = 13
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Receiver responds to introduction (before attachments being sent).
// Actions: Accept, Reject, or (for some reason) Fail.
RESPOND_TO_INTRODUCTION = 14
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Start of the sending attachments phase at sender.
SEND_ATTACHMENTS_START = 15 /*[[*/
/*( device_role = DEVICE_ROLE_INITIATOR, )*/
/*( stage = STAGE_QS_TRANSFER_START )*/
/*]]*/;
// End of sending attachments phase at sender.
SEND_ATTACHMENTS_END = 16 /*[[*/
/*( device_role = DEVICE_ROLE_INITIATOR, )*/
/*( stage = STAGE_QS_TRANSFER_END )*/
/*]]*/;
// Start of the receiving attachments phase at receiver.
RECEIVE_ATTACHMENTS_START = 17
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// End of receiving attachments phase at receiver.
RECEIVE_ATTACHMENTS_END = 18 /*[[*/
/*( device_role = DEVICE_ROLE_REMOTE, )*/
/*( stage = STAGE_QS_TRANSFER_END )*/
/*]]*/;
// Sender cancels sending attachments.
CANCEL_SENDING_ATTACHMENTS = 19
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// Receiver cancels receiving attachments.
CANCEL_RECEIVING_ATTACHMENTS = 20
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Receiver opens received attachments.
OPEN_RECEIVED_ATTACHMENTS = 21
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// User opens the setup activity.
LAUNCH_SETUP_ACTIVITY = 22 [deprecated = true];
// User adds a contact.
ADD_CONTACT = 23;
// User removes a contact.
REMOVE_CONTACT = 24;
// Local devices all Fast Share server.
FAST_SHARE_SERVER_RESPONSE = 25;
// The start of a sending session.
SEND_START = 26 /*[[*/
/*( device_role = DEVICE_ROLE_INITIATOR, )*/
/*( stage = STAGE_QS_CONNECT_START )*/
/*]]*/;
// Receiver accepts a fast initialization.
ACCEPT_FAST_INITIALIZATION = 27
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Set data usage preference.
SET_DATA_USAGE = 28;
// Receiver dismisses a fast initialization
DISMISS_FAST_INITIALIZATION = 29
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Cancel connection.
CANCEL_CONNECTION = 30
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// User starts a chimera activity (e.g. ConsentsChimeraActivity,
// DeviceVisibilityChimeraActivity...)
LAUNCH_ACTIVITY = 31;
// Receiver dismisses a privacy notification.
DISMISS_PRIVACY_NOTIFICATION = 32;
// Receiver taps a privacy notification.
TAP_PRIVACY_NOTIFICATION = 33;
// Receiver taps a help page.
TAP_HELP = 34;
// Receiver taps a feedback.
TAP_FEEDBACK = 35;
// Receiver adds quick settings tile.
ADD_QUICK_SETTINGS_TILE = 36;
// Receiver removes quick settings tile.
REMOVE_QUICK_SETTINGS_TILE = 37;
// Receiver phone consent clicked.
LAUNCH_PHONE_CONSENT = 38;
// Devices show a phone consent banner in Nearby Share Settings
DISPLAY_PHONE_CONSENT = 54;
// Receiver taps quick settings tile.
TAP_QUICK_SETTINGS_TILE = 39;
// Receiver Installation of APKs status.
INSTALL_APK = 40 /*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Receiver verification of APKs status.
VERIFY_APK = 41 /*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// User starts a consent.
LAUNCH_CONSENT = 42;
// After receiving payloads, Nearby Share still needs to transfer the payloads
// to correct attachment formats and move files attachments from temporary
// directory to final destination.
PROCESS_RECEIVED_ATTACHMENTS_END = 43
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Toggle Show Notification setting item in Nearby Share setting.
TOGGLE_SHOW_NOTIFICATION = 44;
// Set device name
SET_DEVICE_NAME = 45;
// users dropped off opt-in page.
DECLINE_AGREEMENTS = 46;
// Request setting permissions (Wifi/BT/location/airplane mode).
REQUEST_SETTING_PERMISSIONS = 47;
// Set up a connection with the remote device.
ESTABLISH_CONNECTION = 48 /*[ stage = STAGE_QS_CONNECT_END ]*/;
// Track device states in Nearby Share setting.
DEVICE_SETTINGS = 49;
// Receiver auto dismisses a fast initialization notification.
AUTO_DISMISS_FAST_INITIALIZATION = 50
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// App Crash event.
// Used only for Windows App now.
APP_CRASH = 51;
// Sender taps the Send button in quick settings
TAP_QUICK_SETTINGS_FILE_SHARE = 52
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// Devices show a privacy notification
DISPLAY_PRIVACY_NOTIFICATION = 53;
// Preference usage event (e.g. load/save preferences, etc).
// Used only for Windows App now.
PREFERENCES_USAGE = 55;
// Default opt in
DEFAULT_OPT_IN = 56;
// The result of the setup wizard flow
SETUP_WIZARD = 57;
// Sender taps a QR code
TAP_QR_CODE = 58 /*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// QR code link shown
QR_CODE_LINK_SHOWN = 59
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// Sender failed to parse endpoint id.
PARSING_FAILED_ENDPOINT_ID = 60
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// The device is discovered by fast initialization
FAST_INIT_DISCOVER_DEVICE = 61
/*[ device_role = DEVICE_ROLE_REMOTE ]*/;
// Send desktop notification.
SEND_DESKTOP_NOTIFICATION = 62;
// User sets account preference
SET_ACCOUNT = 63;
// Decrypt certificate failure
DECRYPT_CERTIFICATE_FAILURE = 64;
// Show allow permission auto access UI
SHOW_ALLOW_PERMISSION_AUTO_ACCESS = 65
/*[ device_role = DEVICE_ROLE_INITIATOR ]*/;
// UI events for transferring files with desktop applications. It includes
// event types such as DESKTOP_TRANSFER_EVENT_SEND_TYPE_SELECT_A_DEVICE.
SEND_DESKTOP_TRANSFER_EVENT = 66;
// Show accept button on Quick Share receive UI
WAITING_FOR_ACCEPT = 67;
// High quality event setup
HIGH_QUALITY_MEDIUM_SETUP = 68;
// RPC call status
RPC_CALL_STATUS = 69;
// A QR code sharing session has started
START_QR_CODE_SESSION = 70;
// A QR code URL has been opened in a web client/browser instead of in a
// native Quick Share app.
QR_CODE_OPENED_IN_WEB_CLIENT = 71;
// A HaTS survey session id has been joined with Quick Share flow id.
HATS_JOINT_EVENT = 72;
// Previews received.
RECEIVE_PREVIEWS = 73;
// LINT.ThenChange(//depot/google3/location/nearby/proto/nearby_event_codes.proto:SharingEventCode)
}
// Event category to differentiate whether this comes from sender or receiver,
// whether this is for communication flow, or for settings.
enum EventCategory {
UNKNOWN_EVENT_CATEGORY = 0;
SENDING_EVENT = 1;
RECEIVING_EVENT = 2;
SETTINGS_EVENT = 3;
RPC_EVENT = 4;
}
// Status of nearby sharing.
enum NearbySharingStatus {
UNKNOWN_NEARBY_SHARING_STATUS = 0;
ON = 1;
OFF = 2;
}
enum Visibility {
UNKNOWN_VISIBILITY = 0;
CONTACTS_ONLY = 1;
EVERYONE = 2;
SELECTED_CONTACTS_ONLY = 3 [deprecated = true];
HIDDEN = 4;
SELF_SHARE = 5;
}
enum DataUsage {
UNKNOWN_DATA_USAGE = 0;
ONLINE = 1;
WIFI_ONLY = 2;
OFFLINE = 3;
}
// The status of establishing a connection. Used by ESTABLISH_CONNECTION.
enum EstablishConnectionStatus {
CONNECTION_STATUS_UNKNOWN = 0;
CONNECTION_STATUS_SUCCESS = 1
/*[ status_bucket = STATUS_SUCCESSFUL ]*/;
CONNECTION_STATUS_FAILURE = 2
/*[ status_bucket = STATUS_INTERNAL_ERROR ]*/;
CONNECTION_STATUS_CANCELLATION = 3
/*[ status_bucket = STATUS_CANCELLED ]*/;
CONNECTION_STATUS_MEDIA_UNAVAILABLE_ATTACHMENT = 4
/*[ status_bucket = STATUS_INTERNAL_ERROR ]*/;
CONNECTION_STATUS_FAILED_PAIRED_KEYHANDSHAKE = 5
/*[ status_bucket = STATUS_INTERNAL_ERROR ]*/;
CONNECTION_STATUS_FAILED_WRITE_INTRODUCTION = 6
/*[ status_bucket = STATUS_INTERNAL_ERROR ]*/;
CONNECTION_STATUS_FAILED_NULL_CONNECTION = 7
/*[ status_bucket = STATUS_INTERNAL_ERROR ]*/;
CONNECTION_STATUS_FAILED_NO_TRANSFER_UPDATE_CALLBACK = 8
/*[ status_bucket = STATUS_INTERNAL_ERROR ]*/;
CONNECTION_STATUS_LOST_CONNECTIVITY = 9
/*[ status_bucket = STATUS_INTERRUPTION ]*/;
// TODO: b/341782941 - : Annote this status when it's confirmed by Nearby
// Connections team.
CONNECTION_STATUS_INVALID_ADVERTISEMENT = 10;
}
// The status of sending and receiving attachments. Used by SEND_ATTACHMENTS.
enum AttachmentTransmissionStatus {
UNKNOWN_ATTACHMENT_TRANSMISSION_STATUS = 0;
COMPLETE_ATTACHMENT_TRANSMISSION_STATUS = 1
/*[ status_bucket = STATUS_SUCCESSFUL ]*/;
CANCELED_ATTACHMENT_TRANSMISSION_STATUS = 2
/*[ status_bucket = STATUS_CANCELLED ]*/;
FAILED_ATTACHMENT_TRANSMISSION_STATUS = 3
/*[ status_bucket = STATUS_INTERNAL_ERROR ]*/;
REJECTED_ATTACHMENT = 4 [deprecated = true];
TIMED_OUT_ATTACHMENT = 5 [deprecated = true];
AWAITING_REMOTE_ACCEPTANCE_FAILED_ATTACHMENT = 6 [deprecated = true];
NOT_ENOUGH_SPACE_ATTACHMENT = 7 [deprecated = true];
FAILED_NO_TRANSFER_UPDATE_CALLBACK = 8 [deprecated = true];
MEDIA_UNAVAILABLE_ATTACHMENT = 9 [deprecated = true];
UNSUPPORTED_ATTACHMENT_TYPE_ATTACHMENT = 10 [deprecated = true];
NO_ATTACHMENT_FOUND = 11 [deprecated = true];
FAILED_NO_SHARE_TARGET_ENDPOINT = 12 [deprecated = true];
FAILED_PAIRED_KEYHANDSHAKE = 13 [deprecated = true];
FAILED_NULL_CONNECTION = 14 [deprecated = true];
FAILED_NO_PAYLOAD = 15 [deprecated = true];
FAILED_WRITE_INTRODUCTION = 16 [deprecated = true];
// The remote response is either missing or has an unknown type.
FAILED_UNKNOWN_REMOTE_RESPONSE = 17 [deprecated = true];
// Breakdowns of FAILED_NULL_CONNECTION (Desktop side)
FAILED_NULL_CONNECTION_INIT_OUTGOING = 18;
FAILED_NULL_CONNECTION_DISCONNECTED = 19;
// Breakdowns of FAILED_NULL_CONNECTION (android side)
// Connection failed due to Wifi is disconnected or Bluetooth setting is off
// or user turn on airplane mode.
FAILED_NULL_CONNECTION_LOST_CONNECTIVITY = 20 [deprecated = true];
// Unexpected connection failure.
FAILED_NULL_CONNECTION_FAILURE = 21 [deprecated = true];
REJECTED_ATTACHMENT_TRANSMISSION_STATUS = 22;
TIMED_OUT_ATTACHMENT_TRANSMISSION_STATUS = 23;
NOT_ENOUGH_SPACE_ATTACHMENT_TRANSMISSION_STATUS = 24
/*[ status_bucket = STATUS_INTERRUPTION ]*/;
UNSUPPORTED_ATTACHMENT_TYPE_ATTACHMENT_TRANSMISSION_STATUS = 25;
FAILED_UNKNOWN_REMOTE_RESPONSE_TRANSMISSION_STATUS = 26;
// Connection failed due to Wifi is disconnected or Bluetooth setting is off
// or user turn on airplane mode.
NO_RESPONSE_FRAME_CONNECTION_CLOSED_LOST_CONNECTIVITY_TRANSMISSION_STATUS = 27
[deprecated = true];
// Unexpected connection failure due to no response frame.
NO_RESPONSE_FRAME_CONNECTION_CLOSED_TRANSMISSION_STATUS = 28;
// Connection failed due to Wifi is disconnected or Bluetooth setting is off
// or user turn on airplane mode.
LOST_CONNECTIVITY_TRANSMISSION_STATUS = 29
/*[ status_bucket = STATUS_INTERRUPTION ]*/;
// Connection failed due to the medium is not allowed.
FAILED_DISALLOWED_MEDIUM = 30;
}
// Generic result status of NearbyConnections API calls.
enum ConnectionLayerStatus {
// No status is available
CONNECTION_LAYER_STATUS_UNKNOWN = 0;
// The operation was successful.
CONNECTION_LAYER_STATUS_SUCCESS = 1;
// The operation failed, without any more information.
CONNECTION_LAYER_STATUS_ERROR = 2;
// The app called an API method out of order (i.e. another method is expected
// to be called first).
CONNECTION_LAYER_STATUS_OUT_OF_ORDER_API_CALL = 3;
// The app already has active operations (advertising, discovering, or
// connected to other devices) with another Strategy. Stop these operations on
// the current Strategy before trying to advertise or discover with a new
// Strategy.
CONNECTION_LAYER_STATUS_ALREADY_HAVE_ACTIVE_STRATEGY = 4;
// The app is already advertising; call StopAdvertising() before trying to
// advertise again.
CONNECTION_LAYER_STATUS_ALREADY_ADVERTISING = 5;
// The app is already discovering; call StopDiscovery() before trying to
// discover again.
CONNECTION_LAYER_STATUS_ALREADY_DISCOVERING = 6;
// NC is already listening for incoming connections from remote endpoints.
CONNECTION_LAYER_STATUS_ALREADY_LISTENING = 7;
// An attempt to read from/write to a connected remote endpoint failed. If
// this occurs repeatedly, consider invoking DisconnectFromEndpoint().
CONNECTION_LAYER_STATUS_END_POINT_IO_ERROR = 8;
// An attempt to interact with a remote endpoint failed because it's unknown
// to us -- it's either an endpoint that was never discovered, or an endpoint
// that never connected to us (both of which are indicative of bad input from
// the client app).
CONNECTION_LAYER_STATUS_END_POINT_UNKNOWN = 9;
// The remote endpoint rejected the connection request.
CONNECTION_LAYER_STATUS_CONNECTION_REJECTED = 10;
// The app is already connected to the specified endpoint. Multiple
// connections to a remote endpoint cannot be maintained simultaneously.
CONNECTION_LAYER_STATUS_ALREADY_CONNECTED_TO_END_POINT = 11;
// The remote endpoint is not connected; messages cannot be sent to it.
CONNECTION_LAYER_STATUS_NOT_CONNECTED_TO_END_POINT = 12;
// There was an error trying to use the device's Bluetooth capabilities.
CONNECTION_LAYER_STATUS_BLUETOOTH_ERROR = 13;
// There was an error trying to use the device's Bluetooth Low Energy
// capabilities.
CONNECTION_LAYER_STATUS_BLE_ERROR = 14;
// There was an error trying to use the device's Wi-Fi capabilities.
CONNECTION_LAYER_STATUS_WIFI_LAN_ERROR = 15;
// An attempt to interact with an in-flight Payload failed because it's
// unknown to us.
CONNECTION_LAYER_STATUS_PAYLOAD_UNKNOWN = 16;
// The connection was reset
CONNECTION_LAYER_STATUS_RESET = 17;
// The connection timed out
CONNECTION_LAYER_STATUS_TIMEOUT = 18;
}
// The status of processing attachments after receiver received payloads
// successfully.
enum ProcessReceivedAttachmentsStatus {
PROCESSING_STATUS_UNKNOWN = 0;
PROCESSING_STATUS_COMPLETE_PROCESSING_ATTACHMENTS = 1;
PROCESSING_STATUS_FAILED_MOVING_FILES = 2;
PROCESSING_STATUS_FAILED_RECEIVING_APK = 3;
PROCESSING_STATUS_FAILED_RECEIVING_TEXT = 4;
PROCESSING_STATUS_FAILED_RECEIVING_WIFI_CREDENTIALS = 5;
}
// The status of advertising and discovering sessions. Used by
// SCAN_FOR_SHARE_TARGETS and ADVERTISE_DEVICE_PRESENCE.
enum SessionStatus {
UNKNOWN_SESSION_STATUS = 0;
SUCCEEDED_SESSION_STATUS = 1
/*[ status_bucket = STATUS_SUCCESSFUL ]*/;
// TODO: b/341782941 - FAILED_SESSION_STATUS occurs when the status of
// advertising or discovering sessions is not successful. It can be
// due to STATUS_INTERNAL_ERROR, STATUS_INTERRUPTION, STATUS_CANCELLED.
// More session statuses should be logged to determine the status.
FAILED_SESSION_STATUS = 2;
}
// User's response to introductions.
enum ResponseToIntroduction {
UNKNOWN_RESPONSE_TO_INTRODUCTION = 0;
ACCEPT_INTRODUCTION = 1;
REJECT_INTRODUCTION = 2;
FAIL_INTRODUCTION = 3;
}
// TODO(fdi): may eventually include desktop, etc.
// The type of a remote device.
enum DeviceType {
UNKNOWN_DEVICE_TYPE = 0;
PHONE = 1;
TABLET = 2;
LAPTOP = 3;
CAR = 4;
FOLDABLE = 5;
XR = 6;
}
// TODO(fdi): may eventually include windows, iOS, etc.
// The OS type of a remote device.
enum OSType {
UNKNOWN_OS_TYPE = 0;
ANDROID = 1;
CHROME_OS = 2;
IOS = 3;
WINDOWS = 4;
MACOS = 5;
}
// Relationship of remote device to sender device.
enum DeviceRelationship {
UNKNOWN_DEVICE_RELATIONSHIP = 0;
// The remote device belongs to the same owner as sender device.
IS_SELF = 1;
// The remote device is a contact of sender.
IS_CONTACT = 2;
// The remote device is a stranger.
IS_STRANGER = 3;
}
// The device sources of the clearcut log.
enum LogSource {
UNSPECIFIED_SOURCE = 0;
// Represents the devices in Nearby labs.
LAB_DEVICES = 1;
// Represents the devices tested by Nearby engs, in the long term can include
// any devices with newest feature flags.
INTERNAL_DEVICES = 2;
// Represents the devices testing our in-development features before they're
// released to the greater public.
BETA_TESTER_DEVICES = 3;
// Represents the OEM partners (like Samsung) that we're working with to
// verify functionality on their devices.
OEM_DEVICES = 4;
// Represents the device for debugging.
DEBUG_DEVICES = 5;
// Represents the device for Nearby Module Food.
NEARBY_MODULE_FOOD_DEVICES = 6;
// Represents the device for BeTo Team Food.
BETO_DOGFOOD_DEVICES = 7;
// Represents the device for Nearby dog Food.
NEARBY_DOGFOOD_DEVICES = 8;
// Represents the device for Nearby Team Food.
NEARBY_TEAMFOOD_DEVICES = 9;
}
// The Fast Share server action name.
enum ServerActionName {
UNKNOWN_SERVER_ACTION = 0;
UPLOAD_CERTIFICATES = 1;
DOWNLOAD_CERTIFICATES = 2;
CHECK_REACHABILITY = 3;
UPLOAD_CONTACTS = 4;
UPDATE_DEVICE_NAME = 5;
UPLOAD_SENDER_CERTIFICATES = 6;
DOWNLOAD_SENDER_CERTIFICATES = 7;
UPLOAD_CONTACTS_AND_CERTIFICATES = 8;
LIST_REACHABLE_PHONE_NUMBERS = 9;
LIST_MY_DEVICES = 10;
LIST_CONTACT_PEOPLE = 11;
// used for analytics logger to record action name.
DOWNLOAD_CERTIFICATES_INFO = 12;
}
// The Fast Share server response state.
enum ServerResponseState {
UNKNOWN_SERVER_RESPONSE_STATE = 0;
SERVER_RESPONSE_SUCCESS = 1;
SERVER_RESPONSE_UNKNOWN_FAILURE = 2;
// For StatusException.
SERVER_RESPONSE_STATUS_OTHER_FAILURE = 3;
SERVER_RESPONSE_STATUS_DEADLINE_EXCEEDED = 4;
SERVER_RESPONSE_STATUS_PERMISSION_DENIED = 5;
SERVER_RESPONSE_STATUS_UNAVAILABLE = 6;
SERVER_RESPONSE_STATUS_UNAUTHENTICATED = 7;
SERVER_RESPONSE_STATUS_INVALID_ARGUMENT = 9;
// For GoogleAuthException.
SERVER_RESPONSE_GOOGLE_AUTH_FAILURE = 8;
// For Internet connect status.
SERVER_RESPONSE_NOT_CONNECTED_TO_INTERNET = 10;
}
// The purpose of requesting the server request.
enum SyncPurpose {
SYNC_PURPOSE_UNKNOWN = 0;
// When NearbySharingChimeraService#sync() is called.
SYNC_PURPOSE_ON_DEMAND_SYNC = 1;
// Requested by chime notification.
SYNC_PURPOSE_CHIME_NOTIFICATION = 2;
// For reqular daily sync.
SYNC_PURPOSE_DAILY_SYNC = 3;
// Wen a device opts into Nearby Share.
SYNC_PURPOSE_OPT_IN_FIRST_SYNC = 4;
// Requested when Nearby Share automatically enables a device that shares
// a single account that has already opted in on another device.
SYNC_PURPOSE_CHECK_DEFAULT_OPT_IN = 5;
// When a device enables Nearby Share.
SYNC_PURPOSE_NEARBY_SHARE_ENABLED = 6;
// When a device is in fast init advertising.
SYNC_PURPOSE_SYNC_AT_FAST_INIT = 7;
// When device start discovery.
SYNC_PURPOSE_SYNC_AT_DISCOVERY = 8;
// When device tries to load valid private certificate.
SYNC_PURPOSE_SYNC_AT_LOAD_PRIVATE_CERTIFICATE = 9;
// When device start advertiseement.
SYNC_PURPOSE_SYNC_AT_ADVERTISEMENT = 10;
// When device contacts list changes.
SYNC_PURPOSE_CONTACT_LIST_CHANGE = 11;
// When showing the C11 banner in Neary Share setting.
SYNC_PURPOSE_SHOW_C11N_VIEW = 12;
// For regular check contact reachability.
SYNC_PURPOSE_REGULAR_CHECK_CONTACT_REACHABILITY = 13;
// When selected contacts list changes in visibility setting.
SYNC_PURPOSE_VISIBILITY_SELECTED_CONTACT_CHANGE = 14;
// When switching account.
SYNC_PURPOSE_ACCOUNT_CHANGE = 15;
// When regenerate certificates
SYNC_PURPOSE_REGENERATE_CERTIFICATES = 16;
// When Device Contacts consent changes
SYNC_PURPOSE_DEVICE_CONTACTS_CONSENT_CHANGE = 17;
}
// The device role to trigger the server request.
enum ClientRole {
CLIENT_ROLE_UNKNOWN = 0;
CLIENT_ROLE_SENDER = 1;
CLIENT_ROLE_RECEIVER = 2;
}
// The type of Nearby Sharing scanning.
enum ScanType {
UNKNOWN_SCAN_TYPE = 0;
FOREGROUND_SCAN = 1;
FOREGROUND_RETRY_SCAN = 2;
DIRECT_SHARE_SCAN = 3;
BACKGROUND_SCAN = 4;
}
// The type of parsing endpoint id failed type.
enum ParsingFailedType {
FAILED_UNKNOWN_TYPE = 0;
// NULL advertisement is returned due to sender failing to parse advertisement
// from endpointInfo byte stream from receiver advertisement.
FAILED_PARSE_ADVERTISEMENT = 1;
// NULL shareTarget is returned due to sender failing to create shareTarget
// from a valid parsed advertisement stemming from issues in certificates, QR
// code tokens or device names.
FAILED_CONVERT_SHARE_TARGET = 2;
}
// The Nearby Sharing advertising mode.
enum AdvertisingMode {
UNKNOWN_ADVERTISING_MODE = 0;
SCREEN_OFF_ADVERTISING_MODE = 1;
BACKGROUND_ADVERTISING_MODE = 2;
MIDGROUND_ADVERTISING_MODE = 3;
FOREGROUND_ADVERTISING_MODE = 4;
SUSPENDED_ADVERTISING_MODE = 5;
}
// The Nearby Sharing discovery mode.
enum DiscoveryMode {
UNKNOWN_DISCOVERY_MODE = 0;
SCREEN_OFF_DISCOVERY_MODE = 1;
BACKGROUND_DISCOVERY_MODE = 2;
MIDGROUND_DISCOVERY_MODE = 3;
FOREGROUND_DISCOVERY_MODE = 4;
SUSPENDED_DISCOVERY_MODE = 5;
}
// The class name of chimera activity.
enum ActivityName {
UNKNOWN_ACTIVITY = 0;
SHARE_SHEET_ACTIVITY = 1;
SETTINGS_ACTIVITY = 2;
RECEIVE_SURFACE_ACTIVITY = 3;
SETUP_ACTIVITY = 4;
DEVICE_VISIBILITY_ACTIVITY = 5;
CONSENTS_ACTIVITY = 6;
SET_DEVICE_NAME_DIALOG = 7;
SET_DATA_USAGE_DIALOG = 8;
QUICK_SETTINGS_ACTIVITY = 9;
REMOTE_COPY_SHARE_SHEET_ACTIVITY = 10;
SETUP_WIZARD_ACTIVITY = 11;
SETTINGS_REVIEW_ACTIVITY = 12;
}
// The type of consent page user launches.
enum ConsentType {
CONSENT_TYPE_UNKNOWN = 0;
// launch Constellation consent page.
CONSENT_TYPE_C11N = 1;
// launch device contact consent page.
CONSENT_TYPE_DEVICE_CONTACT = 2;
}
enum ConsentAcceptanceStatus {
CONSENT_UNKNOWN_ACCEPT_STATUS = 0;
CONSENT_ACCEPTED = 1;
CONSENT_DECLINED = 2;
// unable to enable consent.
CONSENT_UNABLE_TO_ENABLE = 3;
}
enum ApkSource {
UNKNOWN_APK_SOURCE = 0;
APK_FROM_SD_CARD = 1;
INSTALLED_APP = 2;
}
// The Installation status of APK.
enum InstallAPKStatus {
UNKNOWN_INSTALL_APK_STATUS = 0;
FAIL_INSTALLATION = 1;
SUCCESS_INSTALLATION = 2;
}
// The verification status of APK.
enum VerifyAPKStatus {
UNKNOWN_VERIFY_APK_STATUS = 0;
NOT_INSTALLABLE = 1;
INSTALLABLE = 2;
ALREADY_INSTALLED = 3;
}
enum ShowNotificationStatus {
UNKNOWN_SHOW_NOTIFICATION_STATUS = 0;
SHOW = 1;
NOT_SHOW = 2;
}
enum PermissionRequestResult {
PERMISSION_UNKNOWN_REQUEST_RESULT = 0;
PERMISSION_GRANTED = 1;
PERMISSION_REJECTED = 2;
PERMISSION_UNABLE_TO_GRANT = 3;
}
enum PermissionRequestType {
PERMISSION_UNKNOWN_TYPE = 0;
PERMISSION_AIRPLANE_MODE_OFF = 1;
PERMISSION_WIFI = 2;
PERMISSION_BLUETOOTH = 3;
PERMISSION_LOCATION = 4;
PERMISSION_WIFI_HOTSPOT = 5;
}
enum SharingUseCase {
USE_CASE_UNKNOWN = 0;
USE_CASE_NEARBY_SHARE = 1;
USE_CASE_REMOTE_COPY_PASTE = 2;
USE_CASE_WIFI_CREDENTIAL = 3;
USE_CASE_APP_SHARE = 4;
USE_CASE_QUICK_SETTING_FILE_SHARE = 5;
USE_CASE_SETUP_WIZARD = 6;
// Deprecated. QR code is an addition to existing use cases rather than being
// a separate use case.
USE_CASE_NEARBY_SHARE_WITH_QR_CODE = 7 [deprecated = true];
// The user was redirected from Bluetooth sharing UI to Nearby Share
USE_CASE_REDIRECTED_FROM_BLUETOOTH_SHARE = 8;
}
// Used only for Windows App now.
enum AppCrashReason {
APP_CRASH_REASON_UNKNOWN = 0;
}
// Thes source where the attachemnt comes from. It can be an action, app name,
// etc. The first 6 source types are being used as FileSenderType in Nearby
// Share Windows app.
enum AttachmentSourceType {
ATTACHMENT_SOURCE_UNKNOWN = 0;
ATTACHMENT_SOURCE_CONTEXT_MENU = 1;
ATTACHMENT_SOURCE_DRAG_AND_DROP = 2;
ATTACHMENT_SOURCE_SELECT_FILES_BUTTON = 3;
ATTACHMENT_SOURCE_PASTE = 4;
ATTACHMENT_SOURCE_SELECT_FOLDERS_BUTTON = 5;
ATTACHMENT_SOURCE_SHARE_ACTIVATION = 6;
}
// The action to interact with preferences.
// Used only for Windows App now.
enum PreferencesAction {
PREFERENCES_ACTION_UNKNOWN = 0;
PREFERENCES_ACTION_NO_ACTION = 1;
// Primary actions/functions towards preferences
PREFERENCES_ACTION_LOAD_PREFERENCES = 2;
PREFERENCES_ACTION_SAVE_PREFERENCESS = 3;
PREFERENCES_ACTION_ATTEMPT_LOAD = 4;
PREFERENCES_ACTION_RESTORE_FROM_BACKUP = 5;
// Other actions within the 4 actions above
PREFERENCES_ACTION_CREATE_PREFERENCES_PATH = 6;
PREFERENCES_ACTION_MAKE_PREFERENCES_BACKUP_FILE = 7;
PREFERENCES_ACTION_CHECK_IF_PREFERENCES_PATH_EXISTS = 8;
PREFERENCES_ACTION_CHECK_IF_PREFERENCES_INPUT_STREAM_STATUS = 9;
PREFERENCES_ACTION_CHECK_IF_PREFERENCES_FILE_IS_CORRUPTED = 10;
PREFERENCES_ACTION_CHECK_IF_PREFERENCES_BACKUP_FILE_EXISTS = 11;
}
// The status of the action to interact with preferences.
// Used only for Windows App now.
enum PreferencesActionStatus {
PREFERENCES_ACTION_STATUS_UNKNOWN = 0;
PREFERENCES_ACTION_STATUS_SUCCESS = 1;
PREFERENCES_ACTION_STATUS_FAIL = 2;
}
/** The distance of the found nearby fast init advertisement. */
enum FastInitState {
FAST_INIT_UNKNOWN_STATE = 0;
// A device was found in close proximity.
// distance < fast_init_distance_close_centimeters(50 cm)
FAST_INIT_CLOSE_STATE = 1;
// A device was found in far proximity.
// distance < fast_init_distance_close_centimeters(10 m)
FAST_INIT_FAR_STATE = 2;
// No devices have been found nearby. The default state.
FAST_INIT_LOST_STATE = 3;
}
/** The type of FastInit advertisement. */
enum FastInitType {
FAST_INIT_UNKNOWN_TYPE = 0;
// Show HUN to notify the user.
FAST_INIT_NOTIFY_TYPE = 1;
// Not notify the user.
FAST_INIT_SILENT_TYPE = 2;
}
// LINT.IfChange
/** The type of desktop notification event. */
enum DesktopNotification {
DESKTOP_NOTIFICATION_UNKNOWN = 0;
DESKTOP_NOTIFICATION_CONNECTING = 1;
DESKTOP_NOTIFICATION_PROGRESS = 2;
DESKTOP_NOTIFICATION_ACCEPT = 3;
DESKTOP_NOTIFICATION_RECEIVED = 4;
DESKTOP_NOTIFICATION_ERROR = 5;
}
enum DesktopTransferEventType {
DESKTOP_TRANSFER_EVENT_TYPE_UNKNOWN = 0;
// Receive attachments.
DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_ACCEPT = 1;
DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_PROGRESS = 2;
DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_RECEIVED = 3;
DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_ERROR = 4;
// Send attachments.
DESKTOP_TRANSFER_EVENT_SEND_TYPE_START = 5;
DESKTOP_TRANSFER_EVENT_SEND_TYPE_SELECT_A_DEVICE = 6;
DESKTOP_TRANSFER_EVENT_SEND_TYPE_PROGRESS = 7;
DESKTOP_TRANSFER_EVENT_SEND_TYPE_SENT = 8;
DESKTOP_TRANSFER_EVENT_SEND_TYPE_ERROR = 9;
}
enum DecryptCertificateFailureStatus {
DECRYPT_CERT_UNKNOWN_FAILURE = 0;
DECRYPT_CERT_NO_SUCH_ALGORITHM_FAILURE = 1;
DECRYPT_CERT_NO_SUCH_PADDING_FAILURE = 2;
DECRYPT_CERT_INVALID_KEY_FAILURE = 3;
DECRYPT_CERT_INVALID_ALGORITHM_PARAMETER_FAILURE = 4;
DECRYPT_CERT_ILLEGAL_BLOCK_SIZE_FAILURE = 5;
DECRYPT_CERT_BAD_PADDING_FAILURE = 6;
}
// Refer to go/qs-contacts-consent-2024 for the detail.
enum ContactAccess {
CONTACT_ACCESS_UNKNOWN = 0;
CONTACT_ACCESS_NO_CONTACT_UPLOADED = 1;
CONTACT_ACCESS_ONLY_UPLOAD_GOOGLE_CONTACT = 2;
CONTACT_ACCESS_UPLOAD_CONTACT_FOR_DEVICE_CONTACT_CONSENT = 3;
CONTACT_ACCESS_UPLOAD_CONTACT_FOR_QUICK_SHARE_CONSENT = 4;
}
// Refer to go/qs-contacts-consent-2024 for the detail.
enum IdentityVerification {
IDENTITY_VERIFICATION_UNKNOWN = 0;
IDENTITY_VERIFICATION_NO_PHONE_NUMBER_VERIFIED = 1;
IDENTITY_VERIFICATION_PHONE_NUMBER_VERIFIED_NOT_LINKED_TO_GAIA = 2;
IDENTITY_VERIFICATION_PHONE_NUMBER_VERIFIED_LINKED_TO_QS_GAIA = 3;
}
enum ButtonStatus {
BUTTON_STATUS_UNKNOWN = 0;
BUTTON_STATUS_CLICK_ACCEPT = 1;
BUTTON_STATUS_CLICK_REJECT = 2;
BUTTON_STATUS_IGNORE = 3;
}
// LINT.ThenChange(
// //depot/google3/location/nearby/cpp/sharing/clients/dart/platform/lib/types/models.dart
// )
@@ -1,26 +1,41 @@
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Brought from: //depot/google3/location/nearby/sharing/proto/wire_format.proto
// At CL 317565061
// Copyright 2023 Google LLC
//
// 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
//
// https://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.
syntax = "proto2";
//package nearby.sharing.service.proto;
package sharing.nearby;
// Required in Chrome.
// import "storage/datapol/annotations/proto/semantic_annotations.proto";
import "sharing_enums.proto";
option java_package = "com.google.android.gms.nearby.sharing";
option java_outer_classname = "Protocol";
option objc_class_prefix = "GNSHP";
option optimize_for = LITE_RUNTIME;
// File metadata. Does not include the actual bytes of the file.
// NEXT_ID=6
// NEXT_ID=10
message FileMetadata {
enum Type {
UNKNOWN = 0;
IMAGE = 1;
VIDEO = 2;
APP = 3;
ANDROID_APP = 3;
AUDIO = 4;
DOCUMENT = 5;
CONTACT_CARD = 6;
}
// The human readable name of this file (eg. 'Cookbook.pdf').
@@ -43,9 +58,19 @@ message FileMetadata {
// A uuid for the attachment. Should be unique across all attachments.
optional int64 id = 6;
// The parent folder.
optional string parent_folder = 7;
// A stable identifier for the attachment. Used for receiver to identify same
// attachment from different transfers.
optional int64 attachment_hash = 8;
// True, if image in file attachment is sensitive
optional bool is_sensitive_content = 9;
}
// NEXT_ID=5
// NEXT_ID=8
message TextMetadata {
enum Type {
UNKNOWN = 0;
@@ -73,15 +98,19 @@ message TextMetadata {
// A uuid for the attachment. Should be unique across all attachments.
optional int64 id = 6;
// True if text is sensitive, e.g. password
optional bool is_sensitive_text = 7;
}
// NEXT_ID=5
// NEXT_ID=6
message WifiCredentialsMetadata {
enum SecurityType {
UNKNOWN_SECURITY_TYPE = 0;
OPEN = 1;
WPA_PSK = 2;
WEP = 3;
SAE = 4;
}
// The Wifi network name. This will be sent in introduction.
@@ -98,6 +127,49 @@ message WifiCredentialsMetadata {
optional int64 id = 5;
}
// NEXT_ID=8
message AppMetadata {
// The app name. This will be sent in introduction.
optional string app_name = 1;
// The size of the all split of apks.
optional int64 size = 2;
// The File payload id that will be sent as a follow up containing the
// apk paths.
repeated int64 payload_id = 3 [packed = true];
// A uuid for the attachment. Should be unique across all attachments.
optional int64 id = 4;
// The name of apk file. This will be sent in introduction.
repeated string file_name = 5;
// The size of apk file. This will be sent in introduction.
repeated int64 file_size = 6 [packed = true];
// The package name. This will be sent in introduction.
optional string package_name = 7;
}
// NEXT_ID=5
message StreamMetadata {
// A human readable description for the stream.
optional string description = 1;
// The package name of the sending application.
optional string package_name = 2;
// The <TYPE> payload id that will be send as a followup containing the
// ParcelFileDescriptor.
optional int64 payload_id = 3;
// The human-readable name of the package that should be displayed as
// attribution if no other information is available (i.e. the package is not
// installed locally yet).
optional string attributed_app_name = 4;
}
// A frame used when sending messages over the wire.
// NEXT_ID=3
message Frame {
@@ -112,7 +184,7 @@ message Frame {
optional V1Frame v1 = 2;
}
// NEXT_ID=7
// NEXT_ID=8
message V1Frame {
enum FrameType {
UNKNOWN_FRAME_TYPE = 0;
@@ -120,34 +192,59 @@ message V1Frame {
RESPONSE = 2;
PAIRED_KEY_ENCRYPTION = 3;
PAIRED_KEY_RESULT = 4;
// No longer used.
CERTIFICATE_INFO = 5;
CANCEL = 6;
// No longer used.
PROGRESS_UPDATE = 7;
}
optional FrameType type = 1;
// Exactly one of the following fields will be set.
// At most one of the following fields will be set.
optional IntroductionFrame introduction = 2;
optional ConnectionResponseFrame connection_response = 3;
optional PairedKeyEncryptionFrame paired_key_encryption = 4;
optional PairedKeyResultFrame paired_key_result = 5;
optional CertificateInfoFrame certificate_info = 6;
optional CertificateInfoFrame certificate_info = 6 [deprecated = true];
optional ProgressUpdateFrame progress_update = 7 [deprecated = true];
}
// An introduction packet sent by the sending side. Contains a list of files
// they'd like to share.
// NEXT_ID=4
// NEXT_ID=10
message IntroductionFrame {
enum SharingUseCase {
UNKNOWN = 0;
NEARBY_SHARE = 1;
REMOTE_COPY = 2;
}
repeated FileMetadata file_metadata = 1;
repeated TextMetadata text_metadata = 2;
// The required app package to open the content. May be null.
optional string required_package = 3;
repeated WifiCredentialsMetadata wifi_credentials_metadata = 4;
repeated AppMetadata app_metadata = 5;
optional bool start_transfer = 6;
repeated StreamMetadata stream_metadata = 7;
optional SharingUseCase use_case = 8;
repeated int64 preview_payload_ids = 9;
}
// A progress update packet sent by the sending side. Contains transfer progress
// value. NEXT_ID=3
message ProgressUpdateFrame {
optional float progress = 1;
// True, if the receiver should start bandwidth upgrade and receiving the
// payloads.
optional bool start_transfer = 2;
}
// A response packet sent by the receiving side. Accepts or rejects the list of
// files.
// NEXT_ID=2
// NEXT_ID=4
message ConnectionResponseFrame {
enum Status {
UNKNOWN = 0;
@@ -160,10 +257,68 @@ message ConnectionResponseFrame {
// The receiving side's response.
optional Status status = 1;
// Key is attachment hash, value is the details of attachment.
map<int64, AttachmentDetails> attachment_details = 2;
// In the case of a stream attachments, the other side of the pipe.
// Both sender and receiver should validate matching counts.
repeated StreamMetadata stream_metadata = 3;
}
// Attachment details that sent in ConnectionResponseFrame.
// NEXT_ID=3
message AttachmentDetails {
// LINT.IfChange
enum Type {
UNKNOWN = 0;
// Represents FileAttachment.
FILE = 1;
// Represents TextAttachment.
TEXT = 2;
// Represents WifiCredentialsAttachment.
WIFI_CREDENTIALS = 3;
// Represents AppAttachment.
APP = 4;
// Represents StreamAttachment.
STREAM = 5;
}
// LINT.ThenChange(//depot/google3/java/com/google/android/gmscore/integ/client/nearby/src/com/google/android/gms/nearby/sharing/Attachment.java)
// The attachment family type.
optional Type type = 1;
// This field is only for FILE type.
optional FileAttachmentDetails file_attachment_details = 2;
}
// File attachment details included in ConnectionResponseFrame.
// NEXT_ID=3
message FileAttachmentDetails {
// Existing local file size on receiver side.
optional int64 receiver_existing_file_size = 1;
// The key is attachment hash, a stable identifier for the attachment.
// Value is list of payload details transferred for the attachment.
map<int64, PayloadsDetails> attachment_hash_payloads = 2;
}
// NEXT_ID=2
message PayloadsDetails {
// The list should be sorted by creation timestamp.
repeated PayloadDetails payload_details = 1;
}
// Metadata of a payload file created by Nearby Connections.
// NEXT_ID=4
message PayloadDetails {
optional int64 id = 1;
optional int64 creation_timestamp_millis = 2;
optional int64 size = 3;
}
// A paired key encryption packet sent between devices, contains signed data.
// NEXT_ID=3
// NEXT_ID=5
message PairedKeyEncryptionFrame {
// The encrypted data in byte array format.
optional bytes signed_data = 1;
@@ -173,10 +328,17 @@ message PairedKeyEncryptionFrame {
// An optional encrypted data in byte array format.
optional bytes optional_signed_data = 3;
// An optional QR code handshake data in a byte array format.
// For incoming connection contains a signature of the UKEY2
// token, created with the sender's private key.
// For outgoing connection contains an HKDF of the connection token and of the
// UKEY2 token
optional bytes qr_code_handshake_data = 4;
}
// A paired key verification result packet sent between devices.
// NEXT_ID=2
// NEXT_ID=3
message PairedKeyResultFrame {
enum Status {
UNKNOWN = 0;
@@ -187,6 +349,9 @@ message PairedKeyResultFrame {
// The verification result.
optional Status status = 1;
// OS type.
optional location.nearby.proto.sharing.OSType os_type = 2;
}
// A package containing certificate info to be shared to remote device offline.
@@ -229,8 +394,15 @@ message PublicCertificate {
// NEXT_ID=3
message WifiCredentials {
// Wi-Fi password.
optional string password = 1;
optional string password = 1
/* type = ST_ACCOUNT_CREDENTIAL */;
// True if the network is a hidden network that is not broadcasting its SSID.
// Default is false.
optional bool hidden_ssid = 2 [default = false];
}
}
// NEXT_ID=2
message StreamDetails {
// Serialized ParcelFileDescriptor for input stream (for the receiver).
optional bytes input_stream_parcel_file_descriptor_bytes = 1;
}
@@ -1,41 +0,0 @@
//
// ConnectionFailureTracker.swift
// QuickDrop
//
// Created by Leon Böttger on 15.05.25.
//
import Foundation
class ConnectionFailureTracker {
static let shared = ConnectionFailureTracker()
private var failureTimestamps: [Date] = []
private var lastAlertShownAt: Date?
private let queue = DispatchQueue(label: "ConnectionFailureTrackerQueue")
private init() {}
func recordFailure(onTrigger: @escaping () -> Void) {
queue.async {
let now = Date()
// Remove old timestamps older than 10 seconds
self.failureTimestamps = self.failureTimestamps.filter { now.timeIntervalSince($0) < 10 }
self.failureTimestamps.append(now)
// Check if alert can be shown
let canShowAlert = self.failureTimestamps.count >= 2 &&
(self.lastAlertShownAt == nil || now.timeIntervalSince(self.lastAlertShownAt!) >= 60)
if canShowAlert {
self.lastAlertShownAt = now
self.failureTimestamps.removeAll()
DispatchQueue.main.async {
onTrigger()
}
}
}
}
}
File renamed without changes.
+12 -1
View File
@@ -6,8 +6,10 @@
//
import Foundation
import AppKit
import ImageIO
#if os(macOS)
import AppKit
#endif
public class SaveFilesManager {
@@ -88,11 +90,15 @@ public class SaveFilesManager {
progress.totalUnitCount = 10
progress.kind = .file
progress.isPausable = false
#if os(macOS)
progress.publish()
#endif
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
progress.completedUnitCount = 10
#if os(macOS)
progress.unpublish()
#endif
}
try fileManager.removeItem(at: file)
@@ -110,10 +116,12 @@ public class SaveFilesManager {
if !filesFinishedDownloadingSinceLastRun.isEmpty && !isFileTransferRestricted() {
#if os(macOS)
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.openFinderAfterReceiving.rawValue) {
log("Opening \(filesFinishedDownloadingSinceLastRun.count) file(s) in Finder.")
NSWorkspace.shared.activateFileViewerSelecting(filesFinishedDownloadingSinceLastRun)
}
#endif
// Clear the list of finished files
filesFinishedDownloadingSinceLastRun.removeAll()
@@ -143,6 +151,8 @@ public class SaveFilesManager {
if let bookmarkData = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) {
var isStale = false
// Not supported on iOS
#if os(macOS)
do {
let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
@@ -160,6 +170,7 @@ public class SaveFilesManager {
} catch {
log("Failed to resolve bookmark: \(error), using default downloads folder.")
}
#endif
}
do {
+25 -6
View File
@@ -6,12 +6,23 @@
//
import Foundation
#if os(macOS)
import IOKit
import IOKit.pwr_mgt
#elseif os(iOS)
import UIKit
#endif
class SleepManager {
public static let shared = SleepManager()
#if os(macOS)
private var assertionID: IOPMAssertionID = 0
#else
private var assertionID: Int = 0
#endif
private var timer: Timer?
private init() {
@@ -24,27 +35,35 @@ class SleepManager {
}
private func disableSleep() {
if assertionID != 0 {
return
}
if assertionID != 0 { return }
log("[SleepManager] Enabling Wakelock")
#if os(macOS)
IOPMAssertionCreateWithName(
kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString,
IOPMAssertionLevel(kIOPMAssertionLevelOn),
"QuickDrop Data Transfer" as CFString,
&assertionID
)
#elseif os(iOS)
UIApplication.shared.isIdleTimerDisabled = true
assertionID = 1
#endif
}
private func enableSleep() {
if assertionID == 0 {
return
}
if assertionID == 0 { return }
log("[SleepManager] Disabling Wakelock")
#if os(macOS)
IOPMAssertionRelease(assertionID)
#elseif os(iOS)
UIApplication.shared.isIdleTimerDisabled = false
#endif
assertionID = 0
}
-210
View File
@@ -1,210 +0,0 @@
# Google's Nearby Share protocol
This document describes the Nearby Share protocol as understood by me (@grishka) from Chromium sources (that are way too convoluted) and decompiled Google Services Framework apk (that, despite being a decompilation of obfuscated code, is still more helpful than Chromium sources).
The protocol is peer-to-peer, end-to-end encrypted. Overall, it appears like it was mostly designed to run over an unreliable medium like UDP, but I've only observed it over TCP.
Only the WiFi LAN protocol is described here because that's what I reverse engineered, but the data running over other mediums (Bluetooth, WiFi Direct, ...) is most probably largely the same.
If you want to build your own Nearby Share *thing*, you will need:
1. An implementation of multicast DNS (most modern OSes have it built-in)
2. A cryptography library that is capable of ECDSA key exchange, AES-CBC, HMAC, and SHA256. OpenSSL will do but is definitely an overkill.
3. A Protobuf library
4. [These Protobuf files](/NearDrop/ProtobufSource) I collected from the Chromium sources so you don't have to
It is also very helpful to read logcat on your Android device if you're having any trouble. The logging of the Android implementation of Nearby Share is very verbose.
### Device roles
Since a file transfer is unidirectional, the peers are assigned roles:
* The receiving side is the **server**. It listens on a TCP port and advertises an MDNS service.
* The sending side is the **client**. It discovers the MDNS service and connects to the server's TCP port.
### How devices find each other
To become visible in the sheet on Android devices, a *server* advertises an MDNS service. The **domain** is empty. The **type** is `_FC9F5ED42C8A._tcp.`. The **port** is an arbitrary TCP port on which the server accepts incoming connections.
The **name** is the following 10 bytes encoded in URL-safe base64:
* `0x23`, Google calls this "PCP" but I have no idea what it is
* 4-byte **endpoint ID**
* 3-byte **service ID**: `0xFC, 0x9F, 0x5E`
* 2 zero bytes that serve an unknown purpose
The **endpoint ID** is 4 random alphanumeric characters. It identifies devices to each other. Android uses it in its logs quite extensively.
The service also needs to have a TXT record with key `n` and the value of the following encoded in URL-safe base64 ("endpoint info"):
* 1 byte: bit field
* 3 bits: version, set to 0
* 1 bit: visibility, 0 = visible
* 3 bits: device type. Android uses this to pick an icon. 0 = unknown, 1 = phone, 2 = tablet, 3 = laptop
* 1 bit: reserved, set to 0
* 16 bytes of unknown purpose. I set them to random.
* User-visible device name in UTF-8, prefixed with 1-byte length.
Android does not advertise the MDNS service all the time regardless of the visibility setting. It waits for a BLE advertisement with the following parameters:
* Service UUID = `fe 2c`
* Service data = `fc 12 8e 01 42 00 00 00 00 00 00 00 00 00 [10 random bytes]`
This can't be sent from macOS because there's no API I could find that would allow setting the service data. As far as I can tell, the Android side is hardcoded to look for that prefix in the service data so there really is no way to make it work on macOS. Android sends these BLE advertisements periodically while searching for Nearby Share targets; these are also what makes the "device nearby is sharing" notification pop up.
The service ID (FC9F...) comes from SHA256("NearbySharing") = `fc9f5ed42c8a5e9e94684076ef3bf938a809c60ad354992b0435aebbdc58b97b`.
### High-level diagram
```mermaid
sequenceDiagram
Client-->>Server: (Connects to advertised TCP port)
Client->>Server: Connection request
Client->>Server: UKEY2 ClientInit
Server->>Client: UKEY2 ServerInit
Client->>Server: UKEY2 ClientFinish
Server->>Client: Connection response
Client->>Server: Connection response
Note over Server, Client: All following packets are encrypted
Server->>Client: Paired key encryption
Client->>Server: Paired key encryption
Server->>Client: Paired key result
Client->>Server: Paired key result
Client->>Server: Introduction (transfer metadata)
Note over Server: Asks the user
Server->>Client: Response (accept/reject)
Client->>Server: File chunks
Client-->>Server:
Client-->>Server:
Client->>Server: Disconnection
Client-->Server: (Close TCP connection)
```
From the Google point of view, the "Nearby connections" part is a separate universal transport layer, over which the "Share" runs. This may explain some bizarre arrangements where you have protobuf inside protobuf inside encrypted protobuf inside protobuf.
### Frame/packet/message types
There are three types of packets that can appear directly ~~on the wire~~ in the air:
1. **Offline frames.** These are the basic uint of the nearby protocol. They are used to control the connection.
2. **UKEY2 messages.** These are used for the encryption key exchange (UKEY2 is Google's bespoke algorithm for that).
3. **Secure messages.** These are used exclusively after the initial negotiation and carry other packets inside them in the encrypted form.
Keep the protobuf files open to follow along.
### TCP framing
Each protobuf message sent over the TCP connection is prefixed with 4-byte big-endian (MSB first) length.
### Initial handshake & key exchange
(described from the server/recipient point of view)
After the client connects to the server, it sends two packets: a "connection request" and a "UKEY2 client init".
#### Connection request
This is a subtype of "offline frame".
The client wants to connect to the server and tells it about itself. The only field of interest here is `endpointInfo`. It contains the device type and name. It has the same format as the `n` TXT record described above, just without the base64 encoding.
#### UKEY2 client init
This is a subtype of "UKEY2 message". Google's UKEY2 reference implementation [is open source](https://github.com/google/ukey2) and comes with documentation. Please refer to that repo for details on the key exchange.
This is the initial step of the key exchange for end-to-end encryption. Upon receiving this, the server generates a ECDSA key pair and sends its public key in a "server init". The server also needs to remember the raw serialized client init message for the final key derivation step. The outgoing server init message is also needed for the next step. The bytes include the entire protobuf message but do not include the int32 length prefix.
After receiving the server init, the client completes the key derivation and sends a "client finish", containing its public key.
#### UKEY2 client finish
This is where the server completes the key derivation. This step is described in detail [in the Google readme](https://github.com/google/ukey2#deriving-the-authentication-string-and-the-next-protocol-secret).
The result of the key exchange is two values: the authentication string and the next protocol secret.
The **next protocol secret** is further processed to obtain the two 32-byte AES and two 32-byte HMAC keys used for encryption and authentication of further communications ([relevant Chromium code](https://source.chromium.org/chromium/chromium/src/+/main:third_party/securemessage/src/cpp/src/securemessage/crypto_ops.cc) and [this as well](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/ukey2/src/src/main/cpp/src/securegcm/d2d_crypto_ops.cc)):
Derive two 32-byte "device to device" keys using HKDF-SHA256:
* **D2D client key**, using the **next protocol secret** for input key material, `82AA55A0D397F88346CA1CEE8D3909B95F13FA7DEB1D4AB38376B8256DA85510` for salt, and the string `client` for info
* **D2D server key**, using the same parameters, except **info** is `server`
Next, derive the four keys you will use for the actual encryption. All four use the same value of salt, which is `SHA256("SecureMessage")`, or `BF9D2A53C63616D75DB0A7165B91C1EF73E537F2427405FA23610A4BE657642E`. These keys are from the server POV; if you're the client, they need to be swapped around (decrypt/receive shoud use the server key and vice versa).
* **Decrypt key**: IKM = D2D client key, info = `ENC:2`
* **Receive HMAC key**: IKM = D2D client key, info = `SIG:1`
* **Encrypt key**: IKM = D2D server key, info = `ENC:2`
* **Send HMAC key**: IKM = D2D server key, info = `SIG:1`
The key exchange is now complete.
The **authentication string** is used for out-of-band key verification. Nearby Share doesn't use the algorithm specified by UKEY2. Instead, a 4-digit PIN code is generated using [this algorithm](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc;l=215-230;drc=9468ba955b5794fdfaa7301fce4fb70940830c88).
#### Connection response
After the key exchange is complete, the server and client send each other one last plaintext message: a connection response. It's a subtype of offline frame saying that the other party has accepted the connection. All the following communication is encrypted and wrapped in the payload layer.
### The encryption layer
The message type on the wire is always the "secure message". A secure message has two fields: header and body, and the signature.
Header and body is a serialized `HeaderAndBody` message. Inside, there are two fields, that are (what a surprise!) header and body. The body contains the encrypted payload. The header contains the encryption scheme (must be set to `AES256_CBC`), the signature scheme (must be set to `HMAC_SHA256`), the IV (initialization vector) for AES-CBC consisting of 16 random bytes, and the public metadata. Public metadata is needed because the protocol is extensible af. It contains two fields with constant values: version that is always 1 and type that is always `DEVICE_TO_DEVICE_MESSAGE`.
The signature is a HMAC-SHA256 of the header-and-body fields using one of the keys derived above.
The body inside header-and-body is encrypted using AES-CBC with PKCS7 padding. After decryption it should be a valid device to device message (see securegcm.proto). If it isn't, you did something wrong. Go back and debug your key exchange code. Cryptography is messy, don't worry, no one gets it right on the first try ¯\\\_(ツ)\_/¯
The device to device message contains a sequence number and a message. The message is always a serialized offline frame. The sequence number is 1 for the first message, and is incremented with each message. Client and server have their own independent sequence numbers.
### The payload layer
![yo dawg, I heard you like protobuf...](/images/YoDawg.jpg)
This layer allows the transfer of arbitrarily large payloads in chunks. Payloads come in two types: bytes and files. All negotiation uses bytes payloads with protobuf messages inside. The file payloads are used for actual files.
Payload transfer frames are wrapped into offline frames of type `PAYLOAD_TRANSFER`. These are then encrypted as described above. Meaning of the important payload transfer frame fields is as follows:
* **header**: the metadata
* **id**: the payload identifier within the connection. Allows transferring multiple payloads in parallel. You use it to keep track of multiple transfers, associate buffers and files to it, etc.
* **type**: either `BYTES` or `FILE`
* **totalSize**: self-explanatory
* **chunk**: the data itself
* **offset**: the offset at which this chunk needs to be written into the buffer or file
* **flags**: if `LAST_CHUNK` (bit 0) is set, the transfer is complete and this is the last chunk
* **body**: the data bytes themselves
Android does this thing where it sends 2 payload transfer frames in succession for each negotiation message: the first contains the entire message, the second contains 0 bytes but has the `LAST_CHUNK` flag set. I replicated this behavior in NearDrop.
### The "paired key encryption", whatever that is
The client and the server send each other a "paired key encryption" frame, wrapped into the payload layer. *Presumably*, this is used for all the visibility-restriction phone number stuff. Also presumably getting the data contained within involves talking to Google servers. I set `secretIDHash` to 6 random bytes and `signedData` to 72 random bytes in the ones I send. It works fine.
After that, the client and the server send each other a "paired key result" frame. Both have `status` set to `UNABLE`. Whatever.
These and following protobuf messages are specific to Nearby Share and [are defined here](/NearDrop/ProtobufSource/wire_format.proto).
### The transfer metadata aka introduction
After the successful exchange of the meaningless paired key encryption frames, the client sends an "introduction" frame to the server. This contains the list of files that the client is about to send to the server. The fields should be self-explanatory. The `payload_id` will be used in the payload layer for transferring that particluar file.
At this point, Android shows that the connection was successful and displays the PIN code. The server would prompt the user to accept the transfer.
### The file transfer
#### Accepting the transfer
To accept the transfer, the server sends a "response" frame with `status` set to `ACCEPT`. The client will then start sending file chunks over the payload layer. You did it 🎉
#### Rejecting the transfer
Do the same but set `status` to `REJECT`. There are also other status codes, like `NOT_ENOUGH_SPACE`, that result in Android showing a different error.
### Keep-alive frames
Android sends offline frames of type `KEEP_ALIVE` every 10 seconds and expects the server to do the same. If you don't, it will terminate the connection after a while thinking your app crashed or something. This especially comes into play when sending large files. No, TCP's built-in acknowledgements are not enough. There are so many abstraction layers that whoever came up with this forgot about them.
### The "mediums"
Nearby Connections, the underlying layer of Nearby Share, supports running over different "mediums" as Google calls them. Wi-Fi LAN is one of them. Bluetooth, BLE, Wi-Fi Direct, to name a few, are others.
The server is in charge of choosing the medium. The client specifies its supported mediums in its "connection request" packet. The server then intersects that with its own set of supported mediums. After the transfer is accepted, the server may ask the client for a "bandwidth upgrade" by sending a corresponding packet with its chosen medium and authentication credentials, if any.
It is still not clear how the actual medium switch occurs.
+271 -166
View File
@@ -3,49 +3,85 @@
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53B92ABB70840089FD92 /* DeviceListCell.swift */; };
691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 691F53BA2ABB70840089FD92 /* DeviceListCell.xib */; };
691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699DEBA52AB0573200115D22 /* ShareViewController.swift */; };
699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 699DEBA72AB0573200115D22 /* ShareViewController.xib */; };
699DEBAE2AB0573200115D22 /* QuickDropShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */; };
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; };
69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; };
69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */; };
69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF49B2AB8C58500CBE2CC /* SwiftECC */; };
69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; };
69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
EB2B41E42DC63ACC0070FC39 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; };
EB2F9BFB2DA04B69004010A2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */ = {isa = PBXBuildFile; productRef = EB2F9BFD2DA05F48004010A2 /* BezelNotification */; };
EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; };
EB5C36942E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; };
EB5C36E42E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; };
EB5C36E52E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; };
EB5C36E72E3544FF0052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; };
EB5C36E92E3546120052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
EB6E743E2DD5C77700331466 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; };
EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; };
EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; };
EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; };
EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; };
EB5C37BF2E377A780052EFD7 /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
EB5C37C02E377A780052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
EB5C37C12E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
EB5C37C22E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
EB5C37C32E377A780052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
EB5C37C52E377A780052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
EB5C37C62E377A780052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
EB5C37CB2E377A7F0052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D52E377ACF0052EFD7 /* SwiftProtobuf */; };
EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D72E377ACF0052EFD7 /* SwiftECC */; };
EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */; };
EB5C37DC2E377AD60052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37DB2E377AD60052EFD7 /* SwiftECC */; };
EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */; };
EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37EF2E377C3F0052EFD7 /* SwiftECC */; };
EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
EB5C37F32E377CE70052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; };
EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; };
EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; };
EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; };
EB5C383A2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; };
EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; };
EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
EB5C383D2E38C2D90052EFD7 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; };
EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; };
EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; };
EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; };
EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7CD1472D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7F54122D78E96000989564 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54112D78E95C00989564 /* TutorialView.swift */; };
EB7F54142D78E9E700989564 /* LicenseWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54132D78E9E400989564 /* LicenseWindow.swift */; };
EB96ED8B2D7F7CF7004B3FF1 /* SmallSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */; };
@@ -53,7 +89,9 @@
EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; };
EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; };
EBC05C592E32C22200EECC18 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; };
EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; };
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6D2E33B57D00EECC18 /* Converter.swift */; };
@@ -85,20 +123,6 @@
remoteGlobalIDString = 699DEBA02AB0573200115D22;
remoteInfo = ShareExtension;
};
69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
proxyType = 1;
remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC;
remoteInfo = NearbyShare;
};
69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
proxyType = 1;
remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC;
remoteInfo = NearbyShare;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -129,7 +153,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */,
);
name = "Embed Libraries";
runOnlyForDeploymentPostprocessing = 0;
@@ -140,7 +163,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */,
);
name = "Embed Libraries";
runOnlyForDeploymentPostprocessing = 0;
@@ -172,28 +194,24 @@
69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = "<group>"; };
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; };
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; };
69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libNearbyShare.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
EB2B41E32DC63AC80070FC39 /* SleepManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SleepManager.swift; sourceTree = "<group>"; };
EB2F9BFA2DA04B67004010A2 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = "<group>"; };
EB3B73F32DC8FF3A00369951 /* device_to_device_messages.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = device_to_device_messages.proto; sourceTree = "<group>"; };
EB3B73F42DC8FF3A00369951 /* offline_wire_formats.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = offline_wire_formats.proto; sourceTree = "<group>"; };
EB3B73F52DC8FF3A00369951 /* securegcm.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = securegcm.proto; sourceTree = "<group>"; };
EB3B73F62DC8FF3A00369951 /* securemessage.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = securemessage.proto; sourceTree = "<group>"; };
EB3B73F72DC8FF3A00369951 /* ukey.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = ukey.proto; sourceTree = "<group>"; };
EB3B73F82DC8FF3A00369951 /* wire_format.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = wire_format.proto; sourceTree = "<group>"; };
EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
EB5C36952E34EBBD0052EFD7 /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/ShareViewController.xcstrings; sourceTree = "<group>"; };
EB5C36E32E34EC0E0052EFD7 /* License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = License.txt; sourceTree = "<group>"; };
EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveFilesManager.swift; sourceTree = "<group>"; };
EB5C36E82E3546100052EFD7 /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = "<group>"; };
EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionFailureTracker.swift; sourceTree = "<group>"; };
EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickDropiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = justMaybe.mp3; sourceTree = "<group>"; };
EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = "<group>"; };
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
EB7F54112D78E95C00989564 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = "<group>"; };
EB7F54132D78E9E400989564 /* LicenseWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseWindow.swift; sourceTree = "<group>"; };
EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallSheetView.swift; sourceTree = "<group>"; };
EB96ED8E2D7F8103004B3FF1 /* Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = "<group>"; };
EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = sharing_enums.pb.swift; sourceTree = "<group>"; };
EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlertHandler.swift; sourceTree = "<group>"; };
EBC05C6D2E33B57D00EECC18 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = "<group>"; };
EBC05C6E2E33B57D00EECC18 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = "<group>"; };
@@ -210,12 +228,17 @@
EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = QuickDropiOS; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
699DEB9E2AB0573200115D22 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */,
EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */,
EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -224,16 +247,17 @@
buildActionMask = 2147483647;
files = (
EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */,
69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */,
EB5C37DC2E377AD60052EFD7 /* SwiftECC in Frameworks */,
EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
69DCF47F2AB70D0600CBE2CC /* Frameworks */ = {
EB5C37DF2E377C2E0052EFD7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */,
69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */,
EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */,
EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -267,6 +291,7 @@
69DA9A1029E0BF5100A442DA /* QuickDrop */,
699DEBA22AB0573200115D22 /* ShareExtension */,
69DCF4822AB70D0600CBE2CC /* NearbyShare */,
EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */,
698DFAEC29E353220064F247 /* Frameworks */,
69DA9A0F29E0BF5100A442DA /* Products */,
EBC05C8B2E33B59E00EECC18 /* Resources */,
@@ -278,7 +303,7 @@
children = (
69DA9A0E29E0BF5100A442DA /* QuickDrop.app */,
699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */,
69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */,
EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */,
);
name = Products;
sourceTree = "<group>";
@@ -305,6 +330,7 @@
69DA9A2929E18CB500A442DA /* wire_format.pb.swift */,
69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */,
69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */,
EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */,
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */,
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */,
);
@@ -314,35 +340,21 @@
69DCF4822AB70D0600CBE2CC /* NearbyShare */ = {
isa = PBXGroup;
children = (
EB5C37DD2E377B430052EFD7 /* Model */,
EB5C371C2E3669010052EFD7 /* Util */,
EB3B73F92DC8FF3A00369951 /* ProtobufSource */,
69DA9A2729E18CB500A442DA /* Protobuf */,
EB5C371D2E3669150052EFD7 /* Connection */,
);
path = NearbyShare;
sourceTree = "<group>";
};
EB3B73F92DC8FF3A00369951 /* ProtobufSource */ = {
isa = PBXGroup;
children = (
EB3B73F32DC8FF3A00369951 /* device_to_device_messages.proto */,
EB3B73F42DC8FF3A00369951 /* offline_wire_formats.proto */,
EB3B73F52DC8FF3A00369951 /* securegcm.proto */,
EB3B73F62DC8FF3A00369951 /* securemessage.proto */,
EB3B73F72DC8FF3A00369951 /* ukey.proto */,
EB3B73F82DC8FF3A00369951 /* wire_format.proto */,
);
path = ProtobufSource;
sourceTree = "<group>";
};
EB5C371C2E3669010052EFD7 /* Util */ = {
isa = PBXGroup;
children = (
EB96ED8E2D7F8103004B3FF1 /* Localized.swift */,
EB2B41E32DC63AC80070FC39 /* SleepManager.swift */,
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */,
EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */,
EB2F9BFA2DA04B67004010A2 /* Helpers.swift */,
EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */,
);
path = Util;
sourceTree = "<group>";
@@ -352,7 +364,6 @@
children = (
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */,
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */,
EB5C36E82E3546100052EFD7 /* DataModel.swift */,
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */,
698DFAE529E2F91A0064F247 /* NearbyConnection.swift */,
);
@@ -378,6 +389,15 @@
path = Util;
sourceTree = "<group>";
};
EB5C37DD2E377B430052EFD7 /* Model */ = {
isa = PBXGroup;
children = (
69DA9A2729E18CB500A442DA /* Protobuf */,
EB5C36E82E3546100052EFD7 /* DataModel.swift */,
);
path = Model;
sourceTree = "<group>";
};
EBC05C6A2E33B55100EECC18 /* View */ = {
isa = PBXGroup;
children = (
@@ -394,9 +414,9 @@
EBC05C6B2E33B57100EECC18 /* Util */ = {
isa = PBXGroup;
children = (
EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */,
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */,
EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */,
EB96ED8E2D7F8103004B3FF1 /* Localized.swift */,
);
path = Util;
sourceTree = "<group>";
@@ -436,6 +456,7 @@
isa = PBXGroup;
children = (
EB5C36E32E34EC0E0052EFD7 /* License.txt */,
EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */,
EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */,
);
path = Resources;
@@ -443,16 +464,6 @@
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
69DCF47D2AB70D0600CBE2CC /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
699DEBA02AB0573200115D22 /* QuickDropShareExtension */ = {
isa = PBXNativeTarget;
@@ -466,7 +477,6 @@
buildRules = (
);
dependencies = (
69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */,
);
name = QuickDropShareExtension;
productName = ShareExtension;
@@ -488,36 +498,40 @@
);
dependencies = (
699DEBAD2AB0573200115D22 /* PBXTargetDependency */,
69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */,
);
name = QuickDrop;
packageProductDependencies = (
EB2F9BFD2DA05F48004010A2 /* BezelNotification */,
EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */,
EB5C37DB2E377AD60052EFD7 /* SwiftECC */,
);
productName = NearDrop;
productReference = 69DA9A0E29E0BF5100A442DA /* QuickDrop.app */;
productType = "com.apple.product-type.application";
};
69DCF4802AB70D0600CBE2CC /* NearbyShare */ = {
EB5C37E12E377C2E0052EFD7 /* QuickDropiOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */;
buildConfigurationList = EB5C37EA2E377C310052EFD7 /* Build configuration list for PBXNativeTarget "QuickDropiOS" */;
buildPhases = (
69DCF47D2AB70D0600CBE2CC /* Headers */,
69DCF47E2AB70D0600CBE2CC /* Sources */,
69DCF47F2AB70D0600CBE2CC /* Frameworks */,
EB5C37DE2E377C2E0052EFD7 /* Sources */,
EB5C37DF2E377C2E0052EFD7 /* Frameworks */,
EB5C37E02E377C2E0052EFD7 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = NearbyShare;
packageProductDependencies = (
69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */,
69DCF49B2AB8C58500CBE2CC /* SwiftECC */,
fileSystemSynchronizedGroups = (
EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */,
);
productName = NearbyShare;
productReference = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */;
productType = "com.apple.product-type.library.dynamic";
name = QuickDropiOS;
packageProductDependencies = (
EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */,
EB5C37EF2E377C3F0052EFD7 /* SwiftECC */,
);
productName = QuickDropiOS;
productReference = EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -526,8 +540,8 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1430;
LastUpgradeCheck = 1620;
LastSwiftUpdateCheck = 1640;
LastUpgradeCheck = 1640;
TargetAttributes = {
699DEBA02AB0573200115D22 = {
CreatedOnToolsVersion = 14.3.1;
@@ -536,8 +550,8 @@
CreatedOnToolsVersion = 14.3;
LastSwiftMigration = 1430;
};
69DCF4802AB70D0600CBE2CC = {
CreatedOnToolsVersion = 14.3.1;
EB5C37E12E377C2E0052EFD7 = {
CreatedOnToolsVersion = 16.4;
};
};
};
@@ -577,7 +591,7 @@
targets = (
69DA9A0D29E0BF5100A442DA /* QuickDrop */,
699DEBA02AB0573200115D22 /* QuickDropShareExtension */,
69DCF4802AB70D0600CBE2CC /* NearbyShare */,
EB5C37E12E377C2E0052EFD7 /* QuickDropiOS */,
);
};
/* End PBXProject section */
@@ -592,6 +606,7 @@
691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */,
699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */,
EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */,
EB5C383A2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -604,6 +619,15 @@
EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */,
EBC05C8C2E33B59E00EECC18 /* timeIsNow.mp3 in Resources */,
69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */,
EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
EB5C37E02E377C2E0052EFD7 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -623,10 +647,27 @@
EB96ED8B2D7F7CF7004B3FF1 /* SmallSheetView.swift in Sources */,
EBCC27F92D80C9F90079C478 /* Zip.swift in Sources */,
EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */,
EB5C37C52E377A780052EFD7 /* securemessage.pb.swift in Sources */,
EB5C37C62E377A780052EFD7 /* securegcm.pb.swift in Sources */,
EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */,
EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */,
EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */,
EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */,
EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */,
691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */,
EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */,
EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */,
EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */,
EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */,
EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */,
EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -636,11 +677,26 @@
files = (
EBD2A2172D2860C100BCB8E2 /* WelcomeScreen.swift in Sources */,
EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */,
EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */,
EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */,
EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */,
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */,
EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */,
EBC05C732E33B57D00EECC18 /* PlusView.swift in Sources */,
EB5C37BF2E377A780052EFD7 /* ukey.pb.swift in Sources */,
EB5C37C02E377A780052EFD7 /* securemessage.pb.swift in Sources */,
EB5C37C12E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
EB5C37C22E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */,
EB5C37C32E377A780052EFD7 /* wire_format.pb.swift in Sources */,
EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */,
EBC05C782E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */,
EB5C37CB2E377A7F0052EFD7 /* NearbyConnection.swift in Sources */,
EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */,
EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
EBC05C792E33B58100EECC18 /* ApIsolation6Tester.swift in Sources */,
EBC05C7A2E33B58100EECC18 /* ApIsolationTester.swift in Sources */,
EB96ED8C2D7F7E7B004B3FF1 /* SmallSheetView.swift in Sources */,
@@ -648,6 +704,7 @@
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */,
EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */,
EB7F54122D78E96000989564 /* TutorialView.swift in Sources */,
EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */,
EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */,
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
@@ -657,27 +714,29 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
69DCF47E2AB70D0600CBE2CC /* Sources */ = {
EB5C37DE2E377C2E0052EFD7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */,
EBC05C592E32C22200EECC18 /* Localized.swift in Sources */,
EB2F9BFB2DA04B69004010A2 /* Helpers.swift in Sources */,
69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */,
69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */,
EB6E743E2DD5C77700331466 /* ConnectionFailureTracker.swift in Sources */,
69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */,
69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */,
691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */,
69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */,
EB7CD1472D6FB0E800CB0CB5 /* Logger.swift in Sources */,
69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */,
69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */,
EB2B41E42DC63ACC0070FC39 /* SleepManager.swift in Sources */,
EB5C36E92E3546120052EFD7 /* DataModel.swift in Sources */,
EB5C36E72E3544FF0052EFD7 /* SaveFilesManager.swift in Sources */,
69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */,
EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */,
EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */,
EB5C37F32E377CE70052EFD7 /* SaveFilesManager.swift in Sources */,
EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */,
EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */,
EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */,
EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */,
EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */,
EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */,
EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */,
EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */,
EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */,
EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */,
EB5C383D2E38C2D90052EFD7 /* AudioManager.swift in Sources */,
EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */,
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */,
EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -689,16 +748,6 @@
target = 699DEBA02AB0573200115D22 /* QuickDropShareExtension */;
targetProxy = 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */;
};
69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */;
targetProxy = 69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */;
};
69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */;
targetProxy = 69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
@@ -724,7 +773,6 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 92SDAWLN76;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
@@ -757,7 +805,6 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 92SDAWLN76;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist;
@@ -817,6 +864,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 92SDAWLN76;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -840,8 +888,8 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SHARED_BUILD = 56;
SHARED_VERSION = 1.11;
SHARED_BUILD = 61;
SHARED_VERSION = 1.12.2;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -885,6 +933,7 @@
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 92SDAWLN76;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -901,8 +950,8 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SHARED_BUILD = 56;
SHARED_VERSION = 1.11;
SHARED_BUILD = 61;
SHARED_VERSION = 1.12.2;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O";
@@ -922,7 +971,6 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 92SDAWLN76;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QuickDrop/Info.plist;
@@ -963,7 +1011,6 @@
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${SHARED_BUILD}";
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 92SDAWLN76;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = QuickDrop/Info.plist;
@@ -990,37 +1037,75 @@
};
name = Release;
};
69DCF4872AB70D0600CBE2CC /* Debug */ = {
EB5C37EB2E377C310052EFD7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements;
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@executable_path/../Frameworks";
EXECUTABLE_PREFIX = lib;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
69DCF4882AB70D0600CBE2CC /* Release */ = {
EB5C37EC2E377C310052EFD7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements;
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@executable_path/../Frameworks";
EXECUTABLE_PREFIX = lib;
LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)";
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
@@ -1054,11 +1139,11 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */ = {
EB5C37EA2E377C310052EFD7 /* Build configuration list for PBXNativeTarget "QuickDropiOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
69DCF4872AB70D0600CBE2CC /* Debug */,
69DCF4882AB70D0600CBE2CC /* Release */,
EB5C37EB2E377C310052EFD7 /* Debug */,
EB5C37EC2E377C310052EFD7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@@ -1093,21 +1178,41 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf;
};
69DCF49B2AB8C58500CBE2CC /* SwiftECC */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
productName = SwiftECC;
};
EB2F9BFD2DA05F48004010A2 /* BezelNotification */ = {
isa = XCSwiftPackageProductDependency;
package = EB2F9BFC2DA05F48004010A2 /* XCRemoteSwiftPackageReference "BezelNotification" */;
productName = BezelNotification;
};
EB5C37D52E377ACF0052EFD7 /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf;
};
EB5C37D72E377ACF0052EFD7 /* SwiftECC */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
productName = SwiftECC;
};
EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf;
};
EB5C37DB2E377AD60052EFD7 /* SwiftECC */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
productName = SwiftECC;
};
EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */;
productName = SwiftProtobuf;
};
EB5C37EF2E377C3F0052EFD7 /* SwiftECC */ = {
isa = XCSwiftPackageProductDependency;
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
productName = SwiftECC;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 69DA9A0629E0BF5100A442DA /* Project object */;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
LastUpgradeVersion = "1640"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
LastUpgradeVersion = "1640"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
+3 -2
View File
@@ -8,7 +8,6 @@
import AudioToolbox
import BezelNotification
import Cocoa
import NearbyShare
import Network
import StoreKit
import SwiftUI
@@ -305,6 +304,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
DispatchQueue.main.async {
AudioManager.playErrorSound()
NSApp.activate(ignoringOtherApps: true)
let alert = NSAlert()
@@ -338,7 +339,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
activeIncomingTransfers[transfer.id] = TransferInfo(device: device, transfer: transfer)
AudioManager.playSound()
AudioManager.playIncomingFileSound()
let acceptAutomatically = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue)
+373 -156
View File
@@ -2891,103 +2891,103 @@
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "تم إنهاء الاتصال بواسطة هاتفك. إذا حدث ذلك بشكل متكرر، يرجى إعادة تشغيل هاتفك."
"value" : "تم إيقاف الاتصال بواسطة هاتفك."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Die Verbindung wurde von deinem Telefon abgebrochen. Wenn dieses Problem wiederholt auftritt, starte bitte dein Telefon neu."
"value" : "Die Verbindung wurde von deinem Telefon abgebrochen."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "The connection was aborted by your phone. If this happens repeatedly, please restart your phone."
"value" : "The connection was aborted by your phone."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "La conexión fue interrumpida por tu teléfono. Si esto ocurre repetidamente, por favor reinicia tu teléfono."
"value" : "La conexión fue abortada por tu teléfono."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "La connexion a été interrompue par votre téléphone. Si cela se produit plusieurs fois, veuillez redémarrer votre téléphone."
"value" : "La connexion a été interrompue par votre téléphone."
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "A kapcsolatot a telefonja szakította meg. Ha ez ismételten előfordul, kérjük, indítsa újra a telefonját."
"value" : "A kapcsolatot megszakította a telefonod."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "La connessione è stata interrotta dal tuo telefono. Se questo accade ripetutamente, ti preghiamo di riavviare il telefono."
"value" : "La connessione è stata interrotta dal tuo telefono."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "接続が電話によって中断されました。これが繰り返し起こる場合は、電話を再起動してください。"
"value" : "接続はあなたの電話によって中断されました。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "연결이 휴대전화에 의해 중단되었습니다. 이 문제가 반복되면, 휴대전화를 재시작하세요."
"value" : "연결이 휴대폰에 의해 중단되었습니다."
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "De verbinding is afgebroken door je telefoon. Als dit herhaaldelijk gebeurt, start dan je telefoon opnieuw op."
"value" : "De verbinding is door uw telefoon afgebroken."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "A conexão foi abortada pelo seu telefone. Se isso acontecer repetidamente, por favor, reinicie seu telefone."
"value" : "A conexão foi interrompida pelo seu telefone."
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "A ligação foi interrompida pelo seu telemóvel. Se isto acontecer repetidamente, por favor reinicie o seu telemóvel."
"value" : "A ligação foi interrompida pelo seu telemóvel."
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Conexiunea a fost întreruptă de telefonul dumneavoastră. Dacă acest lucru se întâmplă în mod repetat, vă rugăm să reporniți telefonul."
"value" : "Conexiunea a fost întreruptă de telefonul dumneavoastră."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Соединение было прервано вашим телефоном. Если это происходит повторно, пожалуйста, перезагрузите телефон."
"value" : "Соединение было прервано вашим телефоном."
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "การเชื่อมต่อถูกยกเลิกโดยโทรศัพท์ของคุณ หากเกิดขึ้นบ่อย ๆ กรุณารีสตาร์ทโทรศัพท์ของคุณ"
"value" : "การเชื่อมต่อล้มเลิกโดยโทรศัพท์ของคุณ"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "З'єднання було перервано вашим телефоном. Якщо це повторюється, перезапустіть телефон."
"value" : "З'єднання було перервано вашим телефоном."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "连接被您的手机中断。如果此问题频繁出现,请重启您的手机。"
"value" : "连接被您的手机中断。"
}
}
}
@@ -3099,6 +3099,113 @@
}
}
},
"Error.FixInstructions" : {
"extractionState" : "manual",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "إذا حدث ذلك مرارًا، يرجى تعطيل وتمكين الواي فاي على هاتفك، أو إعادة تشغيل هاتفك."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Wenn dies wiederholt passiert, deaktiviere bitte das WLAN auf deinem Telefon und aktiviere es erneut oder starte dein Telefon neu."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "If this happens repeatedly, please disable and re-enable Wi-Fi on your phone, or restart your phone."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Si esto ocurre repetidamente, desactiva y vuelve a activar el Wi-Fi en tu teléfono, o reinicia tu teléfono."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Si cela se produit plusieurs fois, veuillez désactiver et réactiver le Wi-Fi sur votre téléphone ou redémarrez votre téléphone."
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ha ez ismétlődik, kapcsold ki majd be újra a Wi-Fi-t a telefonodon, vagy indítsd újra a telefonod."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se ciò accade ripetutamente, disattiva e riattiva il Wi-Fi sul tuo telefono o riavvia il tuo telefono."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "これが繰り返し起こる場合は、お使いの電話でWi-Fiを無効にして再度有効にするか、電話を再起動してください。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "이 문제가 반복되면 휴대폰에서 Wi-Fi를 비활성화하고 다시 활성화하거나 휴대폰을 재시작하세요."
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Als dit herhaaldelijk gebeurt, schakel dan Wi-Fi op uw telefoon uit en weer in, of start uw telefoon opnieuw op."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se isso acontecer repetidamente, desative e reative o Wi-Fi no seu telefone, ou reinicie o seu telefone."
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se isso acontecer repetidamente, por favor, desative e ative novamente o Wi-Fi do seu telemóvel ou reinicie o seu telemóvel."
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dacă se întâmplă în mod repetat, vă rugăm să dezactivați și să reactivați Wi-Fi pe telefonul dvs. sau să reporniți telefonul."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Если это повторяется, пожалуйста, отключите и снова включите Wi-Fi на вашем телефоне или перезагрузите его."
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "ถ้าเกิดเหตุการณ์นี้บ่อย ๆ กรุณาปิดและเปิด Wi-Fi ในโทรศัพท์ของคุณใหม่ หรือรีสตาร์ทโทรศัพท์ของคุณ"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Якщо це повторюється, будь ласка, вимкніть і знову увімкніть Wi-Fi на вашому телефоні або перезавантажте телефон."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "如果这种情况反复发生,请禁用并重新启用手机上的Wi-Fi,或重启手机。"
}
}
}
},
"Error.NetworkDown" : {
"extractionState" : "manual",
"localizations" : {
@@ -3206,109 +3313,109 @@
}
}
},
"Error.Protocol" : {
"Error.Signature" : {
"extractionState" : "manual",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "خطأ في الاتصال"
"value" : "توقيع غير صالح. عادةً ما يعني هذا أن الرسالة تعرضت للتلف أثناء النقل. يرجى المحاولة مرة أخرى."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fehler bei der Datenübertragung"
"value" : "Ungültige Signatur. Dies bedeutet normalerweise, dass eine Nachricht während der Übertragung beschädigt wurde. Bitte versuche es erneut."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Communication error"
"value" : "Invalid signature. This usually means that a message was damaged during transit. Please try again."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Error de comunicación"
"value" : "Firma no válida. Esto usualmente significa que un mensaje fue dañado durante el tránsito. Por favor, inténtelo de nuevo."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Erreur de communication"
"value" : "Signature invalide. Cela signifie généralement qu'un message a été endommagé pendant le transit. Veuillez réessayer."
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kommunikációs hiba"
"value" : "Érvénytelen aláírás. Ez általában azt jelenti, hogy egy üzenet sérült meg az átvitel során. Kérjük, próbálja újra."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Errore di comunicazione"
"value" : "Firma non valida. Questo di solito significa che un messaggio è stato danneggiato durante la trasmissione. Per favore riprova."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "通信エラー"
"value" : "署名が無効です。これは通常、メッセージが転送中に破損したことを意味します。もう一度試してください。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "통신 오류"
"value" : "잘못된 서명입니다. 보통은 전송 중 메시지가 손상된 것을 의미합니다. 다시 시도해 주세요."
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Communicatiefout"
"value" : "Ongeldige handtekening. Dit betekent meestal dat een bericht beschadigd is tijdens de overdracht. Probeer het opnieuw."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Erro de comunicação"
"value" : "Assinatura inválida. Isso geralmente significa que uma mensagem foi danificada durante a transmissão. Por favor, tente novamente."
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "Erro de comunicação"
"value" : "Assinatura inválida. Isso geralmente significa que uma mensagem foi danificada durante o trânsito. Por favor, tente novamente."
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Eroare de comunicare"
"value" : "Semnătură nevalidă. De obicei, asta înseamnă că un mesaj a fost deteriorat în timpul transmiterii. Vă rugăm să încercați din nou."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ошибка протокола"
"value" : "Недействительная подпись. Обычно это означает, что сообщение было повреждено во время передачи. Пожалуйста, попробуйте еще раз."
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "การสื่อสารผิดพลาด"
"value" : "ลายเซ็นไม่ถูกต้อง ซึ่งมักหมายถึงข้อความได้รับความเสียหายระหว่างการส่ง กรุณาลองใหม่อีกครั้ง"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Помилка протоколу"
"value" : "Недійсний підпис. Зазвичай це означає, що повідомлення було пошкоджене під час передачі. Будь ласка, спробуйте ще раз."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "通信出错"
"value" : "签名无效。这通常意味着消息在传输过程中损坏。请再试一次。"
}
}
}
@@ -3848,6 +3955,113 @@
}
}
},
"LaunchAtLogin" : {
"extractionState" : "manual",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "فتح QuickDrop عند بدء تشغيل Mac"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "QuickDrop beim Starten des Macs öffnen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Open QuickDrop When Starting Mac"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abrir QuickDrop al iniciar Mac"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ouvrir QuickDrop au démarrage du Mac"
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nyissa meg a QuickDropot a Mac indításakor"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Apri QuickDrop all'avvio del Mac"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "Macの起動時にQuickDropを開く"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mac 시작 시 QuickDrop 열기"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Open QuickDrop bij het opstarten van de Mac"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abrir o QuickDrop ao iniciar o Mac"
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abrir QuickDrop ao Iniciar o Mac"
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Deschide QuickDrop la Pornirea Mac-ului"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Открыть QuickDrop при запуске Mac"
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "เปิด QuickDrop เมื่อเริ่มต้น Mac"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Відкрити QuickDrop при запуску Mac"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "启动 Mac 时打开 QuickDrop"
}
}
}
},
"NetworkFilterDescription" : {
"extractionState" : "manual",
"localizations" : {
@@ -4489,6 +4703,113 @@
}
}
},
"OpenFinderAfterReceivingFooter" : {
"extractionState" : "manual",
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "إذا تم تمكين هذا الإعداد، ستفتح نافذة Finder جديدة تلقائيًا بعد النقل، مع تحديد الملفات التي تم تنزيلها للتو من هاتفك إلى جهاز Mac الخاص بك."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Wenn diese Einstellung aktiviert ist, wird nach der Übertragung automatisch ein neues Finder-Fenster geöffnet, das die gerade von deinem Telefon auf deinen Mac heruntergeladenen Dateien hervorhebt."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "If this setting is enabled, a new Finder window will automatically open after the transfer, highlighting the files just downloaded from your phone to your Mac."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Si esta configuración está habilitada, se abrirá automáticamente una nueva ventana del Finder después de la transferencia, resaltando los archivos recién descargados de tu teléfono a tu Mac."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Si ce paramètre est activé, une nouvelle fenêtre du Finder s'ouvrira automatiquement après le transfert, mettant en évidence les fichiers juste téléchargés de votre téléphone vers votre Mac."
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ha ez a beállítás engedélyezve van, a fájlok telefonjáról a Mac-re történő letöltése után automatikusan megnyílik egy új Finder ablak, amely kiemeli az imént letöltött fájlokat."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se questa impostazione è attivata, una nuova finestra del Finder si aprirà automaticamente dopo il trasferimento, evidenziando i file appena scaricati dal tuo telefono al tuo Mac."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "この設定を有効にすると、転送後に新しいFinderウィンドウが自動的に開き、電話からMacにダウンロードされたファイルが強調表示されます。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "이 설정이 활성화되면 전송 후 Finder 새 창이 자동으로 열리고, 휴대폰에서 Mac으로 방금 다운로드된 파일이 강조 표시됩니다."
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Als deze instelling is ingeschakeld, wordt er automatisch een nieuw Finder-venster geopend na de overdracht, waarin de zojuist van je telefoon naar je Mac gedownloade bestanden worden gemarkeerd."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se esta configuração estiver ativada, uma nova janela do Finder será aberta automaticamente após a transferência, destacando os arquivos recém-baixados do seu telefone para o seu Mac."
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "Se esta definição estiver ativada, uma nova janela do Finder abrir-se-á automaticamente após a transferência, destacando os ficheiros acabados de descarregar do seu telemóvel para o seu Mac."
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dacă această setare este activată, o fereastră nouă Finder se va deschide automat după transfer, evidențiind fișierele tocmai descărcate de pe telefonul tău pe Mac-ul tău."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Если эта настройка включена, новое окно Finder автоматически откроется после передачи, выделяя файлы, только что загруженные с вашего телефона на ваш Mac."
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "หากเปิดใช้งานการตั้งค่านี้ หน้าต่าง Finder ใหม่จะเปิดอัตโนมัติหลังจากการถ่ายโอน โดยจะเน้นไฟล์ที่เพิ่งดาวน์โหลดจากโทรศัพท์ของคุณไปยัง Mac"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Якщо увімкнути цей параметр, нове вікно Finder автоматично відкриється після передачі, виділяючи файли, щойно завантажені з вашого телефону на ваш Mac."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "如果启用此设置,传输完成后,将自动打开一个新的访达窗口,高亮显示从手机下载到Mac的文件。"
}
}
}
},
"PinCode" : {
"extractionState" : "manual",
"localizations" : {
@@ -6300,110 +6621,6 @@
}
},
"QuickDrop" : {
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"de" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"es" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"fr" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"hu" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"it" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ja" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ko" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"nl" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ro" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"ru" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"th" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"uk" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "new",
"value" : ""
}
}
},
"shouldTranslate" : false
},
"QuickDropAndroidAppAdvertisement" : {
@@ -8659,103 +8876,103 @@
"ar" : {
"stringUnit" : {
"state" : "translated",
"value" : "الجهاز الآخر لم يستجب في الوقت المحدد. إذا حدثت هذه المشكلة بشكل متكرر، يرجى إعادة تشغيل جهاز الأندرويد الخاص بك."
"value" : "لم يستجب الجهاز الآخر في الوقت المناسب."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Das andere Gerät hat nicht rechtzeitig geantwortet. Wenn dieses Problem wiederholt auftritt, starte bitte dein Android-Gerät neu."
"value" : "Das andere Gerät hat nicht rechtzeitig geantwortet."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "The other device did not respond in time. If this issue occurs repeatedly, please restart your Android device."
"value" : "The other device did not respond in time."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "El otro dispositivo no respondió a tiempo. Si este problema ocurre repetidamente, reinicie su dispositivo Android."
"value" : "El otro dispositivo no respondió a tiempo."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "L'autre appareil n'a pas répondu à temps. Si ce problème se reproduit, redémarrez votre appareil Android."
"value" : "L'autre appareil n'a pas réagi à temps."
}
},
"hu" : {
"stringUnit" : {
"state" : "translated",
"value" : "A másik eszköz nem válaszolt időben. Ha ez a probléma ismétlődik, kérjük, indítsa újra Android eszközét."
"value" : "A másik eszköz nem válaszolt időben."
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "L'altro dispositivo non ha risposto in tempo. Se questo problema si ripete, riavvia il tuo dispositivo Android."
"value" : "L'altro dispositivo non ha risposto in tempo."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "他のデバイスが時間内に応答しませんでした。この問題が繰り返し発生する場合は、Androidデバイスを再起動してください。"
"value" : "他のデバイスが時間内に応答しませんでした。"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "다른 기기가 시간 내에 응답하지 않았습니다. 이 문제가 반복되면 Android 기기를 다시 시작해 주세요."
"value" : "다른 기기가 시간 내에 응답하지 않았습니다."
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Het andere apparaat heeft niet op tijd gereageerd. Als dit probleem zich herhaaldelijk voordoet, start dan uw Android-apparaat opnieuw op."
"value" : "Het andere apparaat reageerde niet op tijd."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"value" : "O outro dispositivo não respondeu a tempo. Se esse problema ocorrer repetidamente, reinicie seu dispositivo Android."
"value" : "O outro dispositivo não respondeu a tempo."
}
},
"pt-PT" : {
"stringUnit" : {
"state" : "translated",
"value" : "O outro dispositivo não respondeu a tempo. Se este problema ocorrer repetidamente, reinicie o seu dispositivo Android."
"value" : "O outro dispositivo não respondeu a tempo."
}
},
"ro" : {
"stringUnit" : {
"state" : "translated",
"value" : "Cealaltă unitate nu a răspuns la timp. Dacă problema apare frecvent, reporniți dispozitivul Android."
"value" : "Celălalt dispozitiv nu a răspuns la timp."
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Другое устройство не ответило вовремя. Если это повторяется, перезагрузите устройство Android."
"value" : "Другое устройство не ответило вовремя."
}
},
"th" : {
"stringUnit" : {
"state" : "translated",
"value" : "อุปกรณ์อื่นไม่ตอบสนองในเวลาที่กำหนด ถ้าเกิดปัญหานี้ซ้ำๆ กรุณารีสตาร์ทอุปกรณ์ Android ของคุณ"
"value" : "อุปกรณ์อีกเครื่องไม่ตอบสนองทันเวลา"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"value" : "Інший пристрій не встиг відповісти. Якщо проблема повторюється, перезапустіть ваш Android пристрій."
"value" : "Інший пристрій не відповів вчасно."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "其他设备未能及时回应。如果此问题重复出现,请重启您的安卓设备。"
"value" : "另一台设备未及时响应。"
}
}
}
-1
View File
@@ -7,7 +7,6 @@
import SwiftUI
import StoreKit
import NearbyShare
let plusViewWidth: CGFloat = 440
let plusViewHeight: CGFloat = 200
+19 -4
View File
@@ -8,17 +8,32 @@
import AVFoundation
class AudioManager {
static var audioPlayer: AVAudioPlayer?
static private var incomingFileSoundPlayer: AVAudioPlayer?
static private var errorSoundPlayer: AVAudioPlayer?
static func playSound() {
static func playIncomingFileSound() {
guard let soundURL = Bundle.main.url(forResource: "timeIsNow", withExtension: "mp3") else {
log("Sound file not found")
return
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL)
audioPlayer?.play()
incomingFileSoundPlayer = try AVAudioPlayer(contentsOf: soundURL)
incomingFileSoundPlayer?.play()
} catch {
log("Error playing sound: \(error.localizedDescription)")
}
}
static func playErrorSound() {
guard let soundURL = Bundle.main.url(forResource: "justMaybe", withExtension: "mp3") else {
log("Sound file not found")
return
}
do {
errorSoundPlayer = try AVAudioPlayer(contentsOf: soundURL)
errorSoundPlayer?.play()
} catch {
log("Error playing sound: \(error.localizedDescription)")
}
+16 -6
View File
@@ -8,7 +8,6 @@
import AudioToolbox
import BezelNotification
import Cocoa
import NearbyShare
import Network
import StoreKit
import SwiftUI
@@ -28,22 +27,31 @@ class ErrorAlertHandler {
NSApp.activate(ignoringOtherApps: true)
var description = ""
let fixInstructions = " " + "Error.FixInstructions".localized()
if let ne = (error as? NearbyError) {
switch ne {
case .inputOutput:
description = "I/O Error"
description = "I/O Error." + fixInstructions
case .protocolError(errorMessage: let errorMessage):
description = "Error.Protocol".localized() + ": \(errorMessage)"
description = errorMessage + fixInstructions
case .packetFilterError:
openAlert(type: .NetworkFilter)
return
case .firewallError:
openAlert(type: .Firewall)
return
case .requiredFieldMissing(errorMessage: let errorMessage):
description = "Error.Protocol".localized() + ": \(errorMessage)"
description = errorMessage + fixInstructions
case .ukey2:
description = "Error.Crypto".localized() + ": \(ne.localizedDescription)"
description = "Error.Crypto".localized() + ": \(ne.localizedDescription)" + fixInstructions
case .canceled(reason: let reason):
description = "Error.Protocol".localized() + ": \(reason.localizedDescription())"
if reason == .timedOut {
description = reason.localizedDescription() + fixInstructions
}
else {
description = reason.localizedDescription()
}
}
} else {
description = error.localizedDescription
@@ -62,6 +70,7 @@ class ErrorAlertHandler {
return
}
else {
AudioManager.playErrorSound()
self.isAlertShown = true
log("Showing alert with message: \"\(alert.messageText)\" and description: \"\(alert.informativeText)\"")
log("Unsuccessful transmission. Already successful transmissions: \(transmissionCount())")
@@ -79,6 +88,7 @@ class ErrorAlertHandler {
func openAlert(type: AlertType) {
log("Opening Alert for \(type)")
AudioManager.playErrorSound()
DispatchQueue.main.async { [self] in
// Create an NSWindow to host the SwiftUI view
+156
View File
@@ -0,0 +1,156 @@
/*
LaunchAtLogin-Modern
https://github.com/sindresorhus/LaunchAtLogin-Modern
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#if os(macOS) || targetEnvironment(macCatalyst)
import SwiftUI
import ServiceManagement
import os.log
@available(macOS 13.0, *)
public enum LaunchAtLogin {
fileprivate static let observable = Observable()
/**
Toggle “launch at login” for your app or check whether it's enabled.
*/
public static var isEnabled: Bool {
get { SMAppService.mainApp.status == .enabled }
set {
observable.objectWillChange.send()
do {
if newValue {
if SMAppService.mainApp.status == .enabled {
try? SMAppService.mainApp.unregister()
}
try SMAppService.mainApp.register()
} else {
try SMAppService.mainApp.unregister()
}
} catch {
log("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)")
}
}
}
/**
Whether the app was launched at login.
- Important: This property must only be checked in `NSApplicationDelegate#applicationDidFinishLaunching`.
*/
public static var wasLaunchedAtLogin: Bool {
let event = NSAppleEventManager.shared().currentAppleEvent
return event?.eventID == kAEOpenApplication
&& event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem
}
}
@available(macOS 13.0, *)
extension LaunchAtLogin {
final class Observable: ObservableObject {
var isEnabled: Bool {
get { LaunchAtLogin.isEnabled }
set {
LaunchAtLogin.isEnabled = newValue
}
}
}
}
@available(macOS 13.0, *)
extension LaunchAtLogin {
/**
This package comes with a `LaunchAtLogin.Toggle` view which is like the built-in `Toggle` but with a predefined binding and label. Clicking the view toggles “launch at login” for your app.
```
struct ContentView: View {
var body: some View {
LaunchAtLogin.Toggle()
}
}
```
The default label is `"Launch at login"`, but it can be overridden for localization and other needs:
```
struct ContentView: View {
var body: some View {
LaunchAtLogin.Toggle {
Text("Launch at login")
}
}
}
```
*/
public struct Toggle<Label: View>: View {
@ObservedObject private var launchAtLogin = LaunchAtLogin.observable
private let label: Label
/**
Creates a toggle that displays a custom label.
- Parameters:
- label: A view that describes the purpose of the toggle.
*/
public init(@ViewBuilder label: () -> Label) {
self.label = label()
}
public var body: some View {
SwiftUI.Toggle(isOn: $launchAtLogin.isEnabled) { label }
}
}
}
@available(macOS 13.0, *)
extension LaunchAtLogin.Toggle<Text> {
/**
Creates a toggle that generates its label from a localized string key.
This initializer creates a ``Text`` view on your behalf with the provided `titleKey`.
- Parameters:
- titleKey: The key for the toggle's localized title, that describes the purpose of the toggle.
*/
public init(_ titleKey: LocalizedStringKey) {
label = Text(titleKey)
}
/**
Creates a toggle that generates its label from a string.
This initializer creates a `Text` view on your behalf with the provided `title`.
- Parameters:
- title: A string that describes the purpose of the toggle.
*/
public init(_ title: some StringProtocol) {
label = Text(title)
}
/**
Creates a toggle with the default title of `Launch at login`.
*/
public init() {
self.init("Launch at login")
}
}
#endif
@@ -62,6 +62,7 @@ func openRouterSettingsPage() async {
guard let routerIP else {
DispatchQueue.main.async {
AudioManager.playErrorSound()
let alert = NSAlert()
alert.alertStyle = .critical
+15 -1
View File
@@ -7,7 +7,6 @@
import SwiftUI
import AppKit
import NearbyShare
struct SettingsView: View {
@@ -46,6 +45,21 @@ struct SettingsView: View {
Toggle("OpenFinderAfterReceiving", isOn: $openFinderAfterReceiving)
.padding(.top, 5)
Text("OpenFinderAfterReceivingFooter")
.font(.footnote)
.padding(.top, 5)
if #available(macOS 13.0, *) {
Divider()
.padding(.vertical, 10)
LaunchAtLogin.Toggle {
Text("LaunchAtLogin")
}
.padding(.top, 5)
}
}
}
}
+1 -2
View File
@@ -6,7 +6,6 @@
//
import SwiftUI
import NearbyShare
struct TutorialView: View {
@@ -87,7 +86,7 @@ struct LargeAppIconView<Content: View>: View {
Image("AppIconHighRes")
.resizable()
.frame(width: 150, height: 150)
.padding(.top, 50)
.padding(.top, 40)
Text(title.localized())
.font(.largeTitle)
-1
View File
@@ -6,7 +6,6 @@
//
import SwiftUI
import NearbyShare
import UniformTypeIdentifiers
struct WelcomeScreen: View {
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+64
View File
@@ -0,0 +1,64 @@
//
// ContentView.swift
// QuickDropiOS
//
// Created by Leon Böttger on 28.07.25.
//
import SwiftUI
struct ContentView: View {
var body: some View {
DeviceListView()
}
}
struct DeviceListView: View {
@StateObject var model = ShareViewModel()
var body: some View {
VStack {
Text("Available Devices")
.font(.title)
.padding()
if model.foundDevices.isEmpty {
Text("Searching for devices...")
.italic()
.padding()
} else {
List(model.foundDevices) { device in
HStack {
Image(systemName: "smartphone")
.resizable()
.frame(width: 30, height: 30)
Text(device.name.isEmpty ? "Android" : device.name)
Spacer()
if model.selectedDevice == device {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
}
}
.contentShape(Rectangle()) // Make entire row tappable
.onTapGesture {
model.selectDevice(device: device)
}
.background(model.selectedDevice == device ? Color.blue.opacity(0.2) : Color.clear)
}
.listStyle(PlainListStyle())
}
}
.frame(minWidth: 300, minHeight: 400)
}
}
struct DeviceListView_Previews: PreviewProvider {
static var previews: some View {
DeviceListView()
}
}
#Preview {
ContentView()
}
+75
View File
@@ -0,0 +1,75 @@
//
// Model.swift
// QuickDrop
//
// Created by Leon Böttger on 28.07.25.
//
import SwiftUI
class ShareViewModel: ObservableObject, ShareExtensionDelegate {
@Published var foundDevices: [RemoteDeviceInfo] = []
@Published var selectedDevice: RemoteDeviceInfo?
@Published var lastError: Error?
@Published var progressState: String? = nil
@Published var progressValue: Double? = nil
private var urls: [URL] = []
private var textToSend: String? = nil
//private var errorAlertHandler = ErrorAlertHandler.shared
private var connectionEstablished = false
private var timeoutDispatchWorkItem: DispatchWorkItem? = nil
init() {
NearbyConnectionManager.shared.startDeviceDiscovery()
}
func addDevice(device: RemoteDeviceInfo) {
withAnimation {
foundDevices.append(device)
}
}
func removeDevice(id: String) {
withAnimation {
foundDevices.removeAll { $0.id == id }
}
}
func connectionWasEstablished(pinCode: String) {
connectionEstablished = true
progressState = String(format: "PinCode".localized(), arguments: [pinCode])
progressValue = 0
}
func connectionFailed(with error: any Error) {
progressValue = nil
lastError = error
//ErrorAlertHandler.shared.showErrorAlert(for: chosenDevice?.name ?? "", error: error)
}
func transferAccepted() {
progressState = "Sending".localized()
}
func transferProgress(progress: Double) {
progressValue = progress
}
func transferFinished() {
progressState = "TransferFinished".localized()
}
func selectDevice(device: RemoteDeviceInfo) {
NearbyConnectionManager.shared.stopDeviceDiscovery()
progressValue = 0
progressState = "Connecting".localized()
selectedDevice = device
NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls, textToSend: textToSend)
}
}
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.multicast</key>
<true/>
</dict>
</plist>
+17
View File
@@ -0,0 +1,17 @@
//
// QuickDropiOSApp.swift
// QuickDropiOS
//
// Created by Leon Böttger on 28.07.25.
//
import SwiftUI
@main
struct QuickDropiOSApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
+182
View File
@@ -255,6 +255,186 @@ OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE PRODUCT OR THE
USE OR OTHER DEALINGS IN OR WITH THE PRODUCT.
Nearby
https://github.com/google/nearby
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
ukey2
https://github.com/google/ukey2
Copyright 2023 Google
@@ -320,6 +500,7 @@ The disclaimer of warranties and limitation of liability provided above shall be
[...]
All sounds are copyrighted material of https://notificationsounds.com
Terms of use: https://notificationsounds.com/terms-of-use
@@ -327,5 +508,6 @@ Terms of use: https://notificationsounds.com/terms-of-use
Sound files:
https://notificationsounds.com/notification-sounds/time-is-now-585
https://notificationsounds.com/notification-sounds/just-maybe-577
Modifications to sound files: Volume and length, no previous modifications.
Binary file not shown.
+2 -1
View File
@@ -7,7 +7,6 @@
import Cocoa
import Foundation
import NearbyShare
import SwiftUI
class ShareViewController: NSViewController, ShareExtensionDelegate {
@@ -204,6 +203,7 @@ class ShareViewController: NSViewController, ShareExtensionDelegate {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
log("Canceling share request because URL \(url) is a directory")
AudioManager.playErrorSound()
let alert = NSAlert()
alert.alertStyle = .critical
@@ -331,6 +331,7 @@ class ShareViewController: NSViewController, ShareExtensionDelegate {
let timeoutAlert = DispatchWorkItem {
if !self.connectionEstablished {
AudioManager.playErrorSound()
let alert = NSAlert()
alert.alertStyle = .critical