mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-15 15:16:12 -04:00
16 KiB
16 KiB
Nearby Sharing Service Linux - Architecture & Implementation Guide
Table of Contents
Architecture Overview
Component Hierarchy
NearbySharingServiceLinux
├── Connections Core (nearby connections layer)
│ ├── ServiceControllerRouter
│ └── Medium Management (BLE, WiFi)
├── Observers (UI/App notifications)
├── Send Surfaces (outgoing transfers)
│ ├── Transfer Callbacks
│ └── Discovery Callbacks
├── Receive Surfaces (incoming transfers)
│ └── Transfer Callbacks
└── Active Transfers
├── Endpoint Mapping
├── Transfer State
└── Attachment Container
Key Classes
NearbySharingServiceLinux: Main service class
- Manages discovery, advertising, and transfers
- Built on top of Nearby Connections Core
- Handles lifecycle of send/receive surfaces
TransferUpdateCallback: Interface for transfer notifications
- Called on status changes (connecting, in-progress, complete)
- Provides progress information
- Reports errors and completion
ShareTargetDiscoveredCallback: Interface for discovery notifications
- Called when devices are found
- Called when devices are lost
- Called when device info updates
AttachmentContainer: Container for files and text
- Manages multiple attachments
- Supports files, text, and WiFi credentials
- Handles attachment lifecycle
How It Works
1. Discovery & Advertising Flow
Sender (Discovers devices):
RegisterSendSurface (Foreground)
↓
StartDiscoveryIfNeeded()
↓
core_->StartDiscovery()
↓
[BLE Scanning Starts]
↓
endpoint_found_cb → ParseAdvertisement()
↓
ShareTarget created
↓
OnShareTargetDiscovered() callback
Receiver (Advertises availability):
RegisterReceiveSurface (Foreground)
↓
StartAdvertisingIfNeeded()
↓
BuildAdvertisement()
↓
core_->StartAdvertising()
↓
[BLE Advertising Starts]
↓
[Visible to nearby senders]
2. Connection Establishment
Sender Receiver
| |
| RequestConnection() |
|------------------------------->|
| | connection_initiated_cb
| | (auto or manual accept)
| connection_initiated_cb |
|<-------------------------------|
| |
| AcceptConnection() | AcceptConnection()
|------------------------------->|
|<-------------------------------|
| |
| connection_accepted_cb | connection_accepted_cb
| |
[Connected - Ready for transfer]
3. File Transfer Flow
Sender Receiver
| |
| SendAttachments() |
| - Create AttachmentContainer |
| - Add FileAttachment |
| |
| RequestConnection() |
|------------------------------->|
| Status: kAwaitingLocalConfirmation
| |
| | Accept()
| |
| AcceptConnection() | AcceptConnection()
| + PayloadListener | + PayloadListener
| |
| Status: kConnecting |
| |
| Send Payloads |
|=============================> |
| (File data chunks) |
| |
| Status: kInProgress |
| Progress: 0% → 100% |
| |
| payload_progress_cb | payload_progress_cb
| |
| Status: kComplete |
| |
4. Advertisement Format
The service creates custom BLE advertisements with device information:
Byte Layout:
[0] Header Byte
- Bits 7-5: Version (3 bits)
- Bit 4: Visibility (0=visible, 1=hidden)
- Bits 3-1: Device Type (3 bits)
- Bit 0: Reserved
[1-2] Salt (2 random bytes)
[3-16] Metadata Key (14 bytes - for encryption)
[17+] TLV Fields (Type-Length-Value)
- Vendor ID (1 byte)
- QR Code data (variable)
- Other metadata
[N+] Device Name (optional, UTF-8)
Device Types:
- 0: Unknown
- 1: Phone
- 2: Tablet
- 3: Laptop
- 4: Unknown
5. State Management
struct TransferState {
AttachmentContainer attachments; // Files/text being transferred
TransferUpdateCallback* callback; // Where to send updates
bool is_incoming; // Direction of transfer
};
// Mappings
endpoint_to_target_ // endpoint_id → ShareTarget
target_id_to_endpoint_ // share_target_id → endpoint_id
active_transfers_ // endpoint_id → TransferState
Implementation Guide
Step 1: Create Service Instance
#include "sharing/linux/nearby_sharing_service_linux.h"
// Create service with custom device name
NearbySharingServiceLinux service("MyLinuxDevice");
// Or let it auto-detect from system
NearbySharingServiceLinux service;
Step 2: Implement Callbacks
class MyTransferCallback : public TransferUpdateCallback {
public:
void OnTransferUpdate(const ShareTarget& share_target,
const AttachmentContainer& attachment_container,
const TransferMetadata& transfer_metadata) override {
// Handle transfer status changes
switch (transfer_metadata.status()) {
case TransferMetadata::Status::kAwaitingLocalConfirmation:
// Incoming transfer - need to accept/reject
HandleIncomingRequest(share_target);
break;
case TransferMetadata::Status::kInProgress:
// Show progress
UpdateProgress(transfer_metadata.progress());
break;
case TransferMetadata::Status::kComplete:
// Transfer done - access attachments
HandleCompletedTransfer(attachment_container);
break;
case TransferMetadata::Status::kFailed:
// Handle error
HandleError();
break;
}
}
};
class MyDiscoveryCallback : public ShareTargetDiscoveredCallback {
public:
void OnShareTargetDiscovered(const ShareTarget& share_target) override {
// New device found
devices_.push_back(share_target);
NotifyUI();
}
void OnShareTargetLost(const ShareTarget& share_target) override {
// Device went away
RemoveDevice(share_target.id);
}
void OnShareTargetUpdated(const ShareTarget& share_target) override {
// Device info changed
UpdateDevice(share_target);
}
};
Step 3: Register Surfaces
MyTransferCallback transfer_callback;
MyDiscoveryCallback discovery_callback;
// To receive files
service.RegisterReceiveSurface(
&transfer_callback,
NearbySharingService::ReceiveSurfaceState::kForeground,
Advertisement::BlockedVendorId::kNone,
[](auto status) {
if (status == NearbySharingService::StatusCodes::kOk) {
std::cout << "Now advertising to nearby devices" << std::endl;
}
});
// To send files
service.RegisterSendSurface(
&transfer_callback,
&discovery_callback,
NearbySharingService::SendSurfaceState::kForeground,
Advertisement::BlockedVendorId::kNone,
false, // don't disable wifi hotspot
[](auto status) {
if (status == NearbySharingService::StatusCodes::kOk) {
std::cout << "Now scanning for nearby devices" << std::endl;
}
});
Step 4: Send Content
// Send a file
void SendFile(int64_t target_id, const std::string& file_path) {
auto container = std::make_unique<AttachmentContainer>();
FileAttachment attachment(FilePath(file_path));
container->AddFileAttachment(std::move(attachment));
service.SendAttachments(target_id, std::move(container),
[](auto status) {
std::cout << "Send status: "
<< NearbySharingService::StatusCodeToString(status)
<< std::endl;
});
}
// Send text
void SendText(int64_t target_id, const std::string& text) {
auto container = std::make_unique<AttachmentContainer>();
TextAttachment attachment(
TextAttachment::Type::TEXT,
text,
std::nullopt, // no title
std::nullopt // no mime type
);
container->AddTextAttachment(std::move(attachment));
service.SendAttachments(target_id, std::move(container),
[](auto status) { /* ... */ });
}
Step 5: Handle Incoming Transfers
void HandleIncomingRequest(const ShareTarget& target) {
// Show confirmation dialog to user
std::cout << "Accept file from " << target.device_name << "? (y/n): ";
char choice;
std::cin >> choice;
if (choice == 'y') {
service.Accept(target.id, [](auto status) {
std::cout << "Accepted!" << std::endl;
});
} else {
service.Reject(target.id, [](auto status) {
std::cout << "Rejected!" << std::endl;
});
}
}
void HandleCompletedTransfer(const AttachmentContainer& container) {
// Process received files
for (const auto& file : container.GetFileAttachments()) {
std::cout << "Received: " << file.file_name() << std::endl;
if (file.file_path().has_value()) {
std::cout << "Saved to: " << file.file_path()->string() << std::endl;
}
}
// Process received text
for (const auto& text : container.GetTextAttachments()) {
std::cout << "Received text: " << text.text_body() << std::endl;
}
}
Code Examples
Example 1: Simple File Sender
#include "sharing/linux/nearby_sharing_service_linux.h"
#include "sharing/file_attachment.h"
#include <thread>
int main() {
NearbySharingServiceLinux service("FileSender");
// Setup callbacks
class SimpleCallback : public TransferUpdateCallback {
void OnTransferUpdate(...) override {
std::cout << "Progress: " << transfer_metadata.progress() * 100 << "%" << std::endl;
}
} transfer_cb;
class SimpleDiscovery : public ShareTargetDiscoveredCallback {
int64_t target_id = -1;
void OnShareTargetDiscovered(const ShareTarget& t) override {
target_id = t.id;
std::cout << "Found: " << t.device_name << std::endl;
}
void OnShareTargetLost(...) override {}
void OnShareTargetUpdated(...) override {}
} discovery_cb;
// Start scanning
service.RegisterSendSurface(&transfer_cb, &discovery_cb,
NearbySharingService::SendSurfaceState::kForeground,
Advertisement::BlockedVendorId::kNone, false, [](auto) {});
// Wait for discovery
std::this_thread::sleep_for(std::chrono::seconds(5));
if (discovery_cb.target_id != -1) {
// Send file
auto container = std::make_unique<AttachmentContainer>();
container->AddFileAttachment(FileAttachment(FilePath("/path/to/file.txt")));
service.SendAttachments(discovery_cb.target_id, std::move(container), [](auto) {});
// Wait for completion
std::this_thread::sleep_for(std::chrono::seconds(10));
}
return 0;
}
Example 2: Auto-Accepting Receiver
class AutoAcceptCallback : public TransferUpdateCallback {
public:
AutoAcceptCallback(NearbySharingServiceLinux* service) : service_(service) {}
void OnTransferUpdate(const ShareTarget& share_target,
const AttachmentContainer& attachment_container,
const TransferMetadata& transfer_metadata) override {
// Auto-accept all incoming transfers
if (transfer_metadata.status() == TransferMetadata::Status::kAwaitingLocalConfirmation) {
service_->Accept(share_target.id, [](auto) {});
}
// Save received files
if (transfer_metadata.status() == TransferMetadata::Status::kComplete) {
for (const auto& file : attachment_container.GetFileAttachments()) {
std::cout << "Saved: " << file.file_name() << std::endl;
}
}
}
private:
NearbySharingServiceLinux* service_;
};
int main() {
NearbySharingServiceLinux service("AutoReceiver");
AutoAcceptCallback callback(&service);
service.RegisterReceiveSurface(&callback,
NearbySharingService::ReceiveSurfaceState::kForeground,
Advertisement::BlockedVendorId::kNone, [](auto) {});
// Keep running
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Best Practices
1. Callback Lifetime Management
// DON'T: Callbacks going out of scope
void BadExample() {
MyTransferCallback callback; // Stack allocated
service.RegisterSendSurface(&callback, ...);
// callback destroyed when function exits!
}
// DO: Keep callbacks alive
class App {
MyTransferCallback callback_; // Member variable
void Setup() {
service.RegisterSendSurface(&callback_, ...);
}
};
2. Error Handling
service.SendAttachments(target_id, container,
[this](NearbySharingService::StatusCodes status) {
switch (status) {
case StatusCodes::kOk:
// Success
break;
case StatusCodes::kInvalidArgument:
// Bad target_id or empty container
LogError("Invalid arguments");
break;
case StatusCodes::kNoAvailableConnectionMedium:
// Bluetooth/WiFi not available
NotifyUserToEnableBluetooth();
break;
default:
LogError("Transfer failed");
break;
}
});
3. Resource Cleanup
class ProperCleanup {
public:
~ProperCleanup() {
// Unregister surfaces before destroying callbacks
service_.UnregisterSendSurface(&transfer_callback_, [](auto) {});
service_.UnregisterReceiveSurface(&transfer_callback_, [](auto) {});
// Shutdown service
service_.Shutdown([](auto) {});
}
private:
NearbySharingServiceLinux service_;
MyTransferCallback transfer_callback_;
};
4. Thread Safety
// The service is NOT thread-safe
// All calls should be from the same thread or synchronized
class ThreadSafeApp {
public:
void SendFromAnyThread(int64_t target_id, const std::string& file) {
task_runner_.PostTask([this, target_id, file]() {
// All service calls happen on same thread
auto container = std::make_unique<AttachmentContainer>();
container->AddFileAttachment(FileAttachment(FilePath(file)));
service_.SendAttachments(target_id, std::move(container), [](auto) {});
});
}
private:
NearbySharingServiceLinux service_;
TaskRunner task_runner_; // Your threading implementation
};
5. State Tracking
class StatefulApp {
public:
void OnTransferUpdate(...) override {
current_state_ = transfer_metadata.status();
// Track progress
if (transfer_metadata.status() == Status::kInProgress) {
progress_map_[share_target.id] = transfer_metadata.progress();
}
// Cleanup on completion
if (TransferMetadata::IsFinalStatus(transfer_metadata.status())) {
progress_map_.erase(share_target.id);
}
}
private:
TransferMetadata::Status current_state_;
std::unordered_map<int64_t, float> progress_map_;
};
Troubleshooting
Discovery Not Working
- Check Bluetooth is enabled:
IsBluetoothPowered() - Verify sender is in foreground state
- Ensure receiver is advertising
- Check for permission issues
Transfers Failing
- Verify file paths are valid and accessible
- Check available disk space on receiver
- Ensure stable Bluetooth connection
- Monitor transfer callbacks for specific error status
Connection Issues
- Devices must be within Bluetooth range (~10m)
- Minimize interference from other BLE devices
- Ensure both devices support required BLE features
- Check firewall settings for WiFi Direct
Performance Tips
- Use appropriate surface states: Background mode when not actively transferring
- Unregister when not needed: Stop scanning/advertising to save battery
- Batch small files: Combine into zip for better efficiency
- Monitor transfer progress: Cancel stalled transfers
- Handle errors gracefully: Retry with exponential backoff