From a209c46fa3b6a9faa9fd8a8c186f201f318ee268 Mon Sep 17 00:00:00 2001 From: kidfromjupiter Date: Wed, 21 Jan 2026 07:50:24 +0530 Subject: [PATCH] added nearby_sharing_app implementing nearby_sharing_service for linxu --- connections/file_share/main.cc | 5 + sharing/linux/BUILD | 104 +++++ sharing/linux/IMPLEMENTATION_GUIDE.md | 592 +++++++++++++++++++++++++ sharing/linux/QUICK_REFERENCE.md | 300 +++++++++++++ sharing/linux/README.md | 348 +++++++++++++++ sharing/linux/nearby_sharing_app.cc | 394 ++++++++++++++++ sharing/linux/nearby_sharing_app_BUILD | 28 ++ sharing/linux/simple_example.cc | 251 +++++++++++ 8 files changed, 2022 insertions(+) create mode 100644 sharing/linux/BUILD create mode 100644 sharing/linux/IMPLEMENTATION_GUIDE.md create mode 100644 sharing/linux/QUICK_REFERENCE.md create mode 100644 sharing/linux/README.md create mode 100644 sharing/linux/nearby_sharing_app.cc create mode 100644 sharing/linux/nearby_sharing_app_BUILD create mode 100644 sharing/linux/simple_example.cc diff --git a/connections/file_share/main.cc b/connections/file_share/main.cc index 44b35113..edeb3970 100644 --- a/connections/file_share/main.cc +++ b/connections/file_share/main.cc @@ -309,6 +309,11 @@ class FileShareApp { : options_.mediums; options.auto_upgrade_bandwidth = true; auto device = CacheDiscoveredDevice(remote_device); + // disable advertising + core_ -> StopAdvertisingV3( + [](nearby::connections::Status status) { + LOG(INFO) << "StopAdvertising status: " << status.ToString(); + }); core_->RequestConnectionV3( local_device_, *device, options, MakeConnectionListener(), [](nearby::connections::Status status) { diff --git a/sharing/linux/BUILD b/sharing/linux/BUILD new file mode 100644 index 00000000..b5942abe --- /dev/null +++ b/sharing/linux/BUILD @@ -0,0 +1,104 @@ +# Copyright 2025 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. + +licenses(["notice"]) + +cc_library( + name = "nearby_sharing_service_linux", + srcs = [ + "nearby_sharing_service_linux.cc", + "//sharing:nearby_sharing_service.cc", + "//sharing:transfer_metadata.cc", + "//sharing:transfer_metadata_builder.cc", + '//sharing/certificates:nearby_share_certificate_manager.cc', + '//sharing/certificates:nearby_share_decrypted_public_certificate.cc', + '//sharing/certificates:nearby_share_encrypted_metadata_key.cc', + '//sharing/certificates:nearby_share_private_certificate.cc', + '//sharing/local_device_data:nearby_share_local_device_data_manager.cc', + '//sharing:nearby_sharing_settings.cc', + '//sharing/analytics:analytics_recorder.cc', + '//sharing:thread_timer.cc', + '//sharing/certificates:common.cc', + '//sharing/common:nearby_share_prefs.cc', + ], + hdrs = [ + "nearby_sharing_service_linux.h", + "//sharing:transfer_metadata.h", + "//sharing:transfer_metadata_builder.h", + '//sharing:nearby_sharing_service.h', + '//sharing/common:nearby_share_prefs.h', + '//sharing/certificates:nearby_share_certificate_manager.h', + '//sharing/certificates:nearby_share_decrypted_public_certificate.h', + '//sharing/certificates:nearby_share_encrypted_metadata_key.h', + '//sharing/certificates:nearby_share_private_certificate.h', + '//sharing/internal/api:private_certificate_data.h', + '//sharing/local_device_data:nearby_share_local_device_data_manager.h', + '//sharing:nearby_sharing_settings.h', + '//sharing/analytics:analytics_recorder.h', + '//sharing/analytics:analytics_device_settings.h', + '//sharing/analytics:analytics_information.h', + '//sharing/internal/api:preference_manager.h', + '//sharing/internal/public:context.h', + '//sharing/internal/public:pref_names.h', + '//sharing/internal/api:bluetooth_adapter.h', + '//sharing/internal/api:fast_initiation_manager.h', + '//sharing/internal/api:fast_init_ble_beacon.h', + '//sharing/internal/public:connectivity_manager.h', + '//sharing:thread_timer.h', + '//sharing:share_target_discovered_callback.h', + '//sharing:transfer_update_callback.h', + '//sharing/certificates:common.h', + '//sharing/certificates:constants.h', + ], + visibility = ["//visibility:public"], + deps = [ + "//connections:core", + "//connections:core_types", + "//internal/platform:base", + "//internal/platform:comm", + "//internal/platform:types", + "//internal/platform/implementation:platform", + "//internal/platform/implementation/linux:linux", + "//sharing:attachments", + "//sharing:types", + "//sharing/proto:share_cc_proto", + "@com_google_absl//absl/strings", + ], +) + +cc_binary( + name = "nearby_sharing_app", + srcs = ["nearby_sharing_app.cc"], + deps = [ + ":nearby_sharing_service_linux", + "//sharing:attachments", + "//sharing:types", + "//internal/platform:base", + "@com_google_absl//absl/strings", + ], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "simple_example", + srcs = ["simple_example.cc"], + deps = [ + ":nearby_sharing_service_linux", + "//sharing:attachments", + "//sharing:types", + "//internal/platform:base", + "@com_google_absl//absl/strings", + ], + visibility = ["//visibility:public"], +) diff --git a/sharing/linux/IMPLEMENTATION_GUIDE.md b/sharing/linux/IMPLEMENTATION_GUIDE.md new file mode 100644 index 00000000..02b69089 --- /dev/null +++ b/sharing/linux/IMPLEMENTATION_GUIDE.md @@ -0,0 +1,592 @@ +# Nearby Sharing Service Linux - Architecture & Implementation Guide + +## Table of Contents +1. [Architecture Overview](#architecture-overview) +2. [How It Works](#how-it-works) +3. [Implementation Guide](#implementation-guide) +4. [Code Examples](#code-examples) +5. [Best Practices](#best-practices) + +## 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 + +```cpp +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 + +```cpp +#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 + +```cpp +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 + +```cpp +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 + +```cpp +// Send a file +void SendFile(int64_t target_id, const std::string& file_path) { + auto container = std::make_unique(); + + 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(); + + 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 + +```cpp +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 + +```cpp +#include "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/file_attachment.h" +#include + +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(); + 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 + +```cpp +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 + +```cpp +// 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 + +```cpp +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 + +```cpp +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 + +```cpp +// 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(); + 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 + +```cpp +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 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 + +1. **Use appropriate surface states**: Background mode when not actively transferring +2. **Unregister when not needed**: Stop scanning/advertising to save battery +3. **Batch small files**: Combine into zip for better efficiency +4. **Monitor transfer progress**: Cancel stalled transfers +5. **Handle errors gracefully**: Retry with exponential backoff + diff --git a/sharing/linux/QUICK_REFERENCE.md b/sharing/linux/QUICK_REFERENCE.md new file mode 100644 index 00000000..22e17efa --- /dev/null +++ b/sharing/linux/QUICK_REFERENCE.md @@ -0,0 +1,300 @@ +# Nearby Sharing Linux - Quick Reference + +## Quick Start + +### Build +```bash +bazel build //sharing/linux:simple_example +bazel build //sharing/linux:nearby_sharing_app +``` + +### Run Simple Example +```bash +# Terminal 1 (Receiver) +./bazel-bin/sharing/linux/simple_example receiver + +# Terminal 2 (Sender) +./bazel-bin/sharing/linux/simple_example sender "Hello World!" +``` + +### Run Full App +```bash +./bazel-bin/sharing/linux/nearby_sharing_app [device_name] +``` + +## API Cheat Sheet + +### Include Headers +```cpp +#include "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/attachment_container.h" +#include "sharing/file_attachment.h" +#include "sharing/text_attachment.h" +#include "sharing/share_target.h" +#include "sharing/transfer_metadata.h" +``` + +### Create Service +```cpp +using namespace nearby::sharing::linux; +NearbySharingServiceLinux service("DeviceName"); +``` + +### Implement Callbacks +```cpp +// Transfer updates +class MyCallback : public TransferUpdateCallback { + void OnTransferUpdate(const ShareTarget& target, + const AttachmentContainer& attachments, + const TransferMetadata& metadata) override { + // Handle status changes + } +}; + +// Device discovery +class MyDiscovery : public ShareTargetDiscoveredCallback { + void OnShareTargetDiscovered(const ShareTarget& target) override { } + void OnShareTargetLost(const ShareTarget& target) override { } + void OnShareTargetUpdated(const ShareTarget& target) override { } +}; +``` + +### Register to Receive +```cpp +service.RegisterReceiveSurface( + &transfer_callback, + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + [](auto status) { /* callback */ }); +``` + +### Register to Send +```cpp +service.RegisterSendSurface( + &transfer_callback, + &discovery_callback, + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + false, // disable_wifi_hotspot + [](auto status) { /* callback */ }); +``` + +### Send File +```cpp +auto container = std::make_unique(); +container->AddFileAttachment(FileAttachment(FilePath("/path/to/file"))); +service.SendAttachments(target_id, std::move(container), [](auto) {}); +``` + +### Send Text +```cpp +auto container = std::make_unique(); +container->AddTextAttachment(TextAttachment( + TextAttachment::Type::TEXT, "message", std::nullopt, std::nullopt)); +service.SendAttachments(target_id, std::move(container), [](auto) {}); +``` + +### Accept/Reject/Cancel +```cpp +service.Accept(target_id, [](auto status) {}); +service.Reject(target_id, [](auto status) {}); +service.Cancel(target_id, [](auto status) {}); +``` + +### Check Status +```cpp +bool scanning = service.IsScanning(); +bool transferring = service.IsTransferring(); +bool bt_present = service.IsBluetoothPresent(); +bool bt_powered = service.IsBluetoothPowered(); +``` + +### Shutdown +```cpp +service.Shutdown([](auto status) {}); +``` + +## Transfer Statuses + +| Status | Meaning | Action | +|--------|---------|--------| +| `kConnecting` | Establishing connection | Wait | +| `kAwaitingLocalConfirmation` | Need to accept/reject | Call Accept() or Reject() | +| `kAwaitingRemoteAcceptance` | Waiting for remote | Wait | +| `kInProgress` | Transferring data | Show progress | +| `kComplete` | Success | Access attachments | +| `kFailed` | Error occurred | Check logs | +| `kRejected` | User rejected | Retry or cancel | +| `kCancelled` | Transfer cancelled | Cleanup | +| `kTimedOut` | Connection timeout | Retry | + +## Status Codes + +| Code | Meaning | +|------|---------| +| `kOk` | Success | +| `kError` | General error | +| `kOutOfOrderApiCall` | API called incorrectly | +| `kTransferAlreadyInProgress` | Can't start new transfer | +| `kNoAvailableConnectionMedium` | No Bluetooth/WiFi | +| `kInvalidArgument` | Bad parameters | + +## Common Patterns + +### Auto-Accept Pattern +```cpp +class AutoAccept : public TransferUpdateCallback { + void OnTransferUpdate(...) override { + if (metadata.status() == Status::kAwaitingLocalConfirmation) { + service_->Accept(target.id, [](auto) {}); + } + } +}; +``` + +### Progress Tracking Pattern +```cpp +void OnTransferUpdate(...) override { + if (metadata.status() == Status::kInProgress) { + int percent = metadata.progress() * 100; + uint64_t bytes = metadata.transferred_bytes(); + std::cout << percent << "% (" << bytes << " bytes)" << std::endl; + } +} +``` + +### Device Selection Pattern +```cpp +std::vector devices; + +void OnShareTargetDiscovered(const ShareTarget& target) override { + devices.push_back(target); + std::cout << devices.size() << ". " << target.device_name << std::endl; +} + +void SendToDevice(size_t index) { + if (index < devices.size()) { + SendFile(devices[index].id, file_path); + } +} +``` + +### Error Handling Pattern +```cpp +service.SendAttachments(target_id, container, + [](NearbySharingService::StatusCodes status) { + if (status != StatusCodes::kOk) { + std::cerr << "Error: " + << NearbySharingService::StatusCodeToString(status) + << std::endl; + return; + } + std::cout << "Transfer initiated" << std::endl; + }); +``` + +## Debugging Tips + +### Enable Verbose Logging +```cpp +// Set environment variable +export NEARBY_LOGS=VERBOSE +``` + +### Check Bluetooth +```cpp +if (!service.IsBluetoothPresent()) { + std::cout << "No Bluetooth adapter found" << std::endl; +} +if (!service.IsBluetoothPowered()) { + std::cout << "Bluetooth is off" << std::endl; +} +``` + +### Dump Service State +```cpp +std::cout << service.Dump() << std::endl; +// Output: "NearbySharingServiceLinux advertising=true scanning=false ..." +``` + +### Monitor Callbacks +```cpp +void OnTransferUpdate(...) override { + std::cout << "[Transfer] " << target.device_name + << " - " << TransferMetadata::StatusToString(metadata.status()) + << " - " << (metadata.progress() * 100) << "%" << std::endl; +} +``` + +## File Locations + +- **Service**: `sharing/linux/nearby_sharing_service_linux.{h,cc}` +- **Simple Example**: `sharing/linux/simple_example.cc` +- **Full App**: `sharing/linux/nearby_sharing_app.cc` +- **README**: `sharing/linux/README.md` +- **Implementation Guide**: `sharing/linux/IMPLEMENTATION_GUIDE.md` +- **BUILD**: `sharing/linux/BUILD` + +## Common Issues + +### "No devices found" +- Ensure receiver is running and advertising +- Check Bluetooth is enabled on both devices +- Verify devices are within range (~10m) +- Try restarting Bluetooth + +### "Transfer failed" +- Check file permissions +- Verify disk space +- Ensure stable connection +- Check firewall settings + +### "Invalid argument" +- Verify target_id is valid +- Ensure container has attachments +- Check surface is registered + +### Callback not called +- Verify callback lifetime (must outlive service) +- Check registration was successful +- Ensure main thread/event loop is running + +## Example Workflows + +### Send File Workflow +``` +1. Create service +2. Create callbacks +3. RegisterSendSurface (foreground) +4. Wait for OnShareTargetDiscovered +5. Create AttachmentContainer +6. Add FileAttachment +7. SendAttachments(target_id, container) +8. Wait for kComplete in OnTransferUpdate +``` + +### Receive File Workflow +``` +1. Create service +2. Create callback +3. RegisterReceiveSurface (foreground) +4. Wait for kAwaitingLocalConfirmation +5. Call Accept(target_id) +6. Wait for kInProgress updates +7. Wait for kComplete +8. Access files from AttachmentContainer +``` + +## Performance Notes + +- **Scanning**: Consumes battery, stop when not needed +- **Advertising**: Minimal impact +- **Transfer**: WiFi Direct faster than Bluetooth +- **File Size**: Large files (>100MB) benefit from WiFi +- **Small Files**: Bluetooth sufficient for <10MB + +## Links + +- [README.md](README.md) - Overview and features +- [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) - Detailed architecture +- [nearby_sharing_service.h](../nearby_sharing_service.h) - Base interface diff --git a/sharing/linux/README.md b/sharing/linux/README.md new file mode 100644 index 00000000..9545e0a1 --- /dev/null +++ b/sharing/linux/README.md @@ -0,0 +1,348 @@ +# Nearby Sharing Linux Implementation + +This directory contains the Linux-specific implementation of Nearby Sharing and a sample application demonstrating its usage. + +## Overview + +Nearby Sharing is a feature that allows users to share files, text, and other content between nearby devices using Bluetooth Low Energy (BLE) and Wi-Fi Direct. This implementation provides a simplified Linux interface built on top of the Nearby Connections API. + +## Components + +### NearbySharingServiceLinux + +The main service class that provides nearby sharing functionality: + +- **Discovery & Advertising**: Find nearby devices and advertise your device's availability +- **File Transfer**: Send and receive files +- **Text Transfer**: Send and receive text messages +- **Connection Management**: Handle connection lifecycle (accept, reject, cancel) + +### Key Concepts + +#### 1. Send Surface +Represents the sending side of a transfer: +- **Foreground**: Actively scans for nearby devices +- **Background**: Only listens for transfer updates without scanning + +#### 2. Receive Surface +Represents the receiving side of a transfer: +- **Foreground**: Advertises to everyone, visible to all nearby devices +- **Background**: Advertises only to contacts (limited visibility) + +#### 3. Callbacks + +**TransferUpdateCallback**: Receives updates about ongoing transfers +- Status changes (connecting, in progress, complete, failed) +- Progress updates +- Transfer metadata (speed, bytes transferred, etc.) + +**ShareTargetDiscoveredCallback**: Receives notifications about discovered devices +- Device discovered +- Device lost (out of range) +- Device updated + +#### 4. Attachments + +**FileAttachment**: Represents a file to be transferred +- Requires a file path +- Automatically determines MIME type and size + +**TextAttachment**: Represents text content to be transferred +- Supports plain text, URLs, addresses, and phone numbers +- Includes optional title and MIME type + +## Sample Application + +The `nearby_sharing_app.cc` demonstrates how to use the service: + +### Building + +```bash +# Build the sample application +bazel build //sharing/linux:nearby_sharing_app +``` + +### Running + +```bash +# Run with default device name +./bazel-bin/sharing/linux/nearby_sharing_app + +# Run with custom device name +./bazel-bin/sharing/linux/nearby_sharing_app "MyCustomName" +``` + +### Features + +1. **Start as Receiver**: Advertise your device to receive files +2. **Start as Sender**: Discover nearby devices to send files +3. **List Discovered Devices**: View all devices found during scanning +4. **Send File**: Transfer a file to a discovered device +5. **Send Text**: Send text content to a discovered device +6. **Accept/Reject**: Handle incoming transfer requests +7. **Cancel Transfer**: Cancel an ongoing transfer +8. **Status Info**: View Bluetooth and service status + +## Usage Examples + +### Example 1: Send a File + +**Device A (Sender)**: +```cpp +NearbySharingApp app("Sender-Device"); + +// Start scanning for devices +app.StartAsSender(); + +// Wait for discovery... +std::this_thread::sleep_for(std::chrono::seconds(3)); + +// List discovered devices +app.ListDiscoveredDevices(); + +// Send file to target with ID 1 +app.SendFile(1, "/path/to/file.txt"); +``` + +**Device B (Receiver)**: +```cpp +NearbySharingApp app("Receiver-Device"); + +// Start advertising +app.StartAsReceiver(); + +// When connection is initiated (via callback), accept it +// This happens automatically when you see OnTransferUpdate with +// Status::kAwaitingLocalConfirmation +app.AcceptIncomingShare(target_id); +``` + +### Example 2: Send Text + +```cpp +NearbySharingApp app("Text-Sender"); + +// Start as sender +app.StartAsSender(); + +// Wait for device discovery +std::this_thread::sleep_for(std::chrono::seconds(2)); + +// Send text to discovered device +app.SendText(1, "Hello from Nearby Sharing!"); +``` + +### Example 3: Custom Callbacks + +```cpp +class CustomTransferCallback : public TransferUpdateCallback { + public: + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + switch (transfer_metadata.status()) { + case TransferMetadata::Status::kAwaitingLocalConfirmation: + // Auto-accept incoming transfers + service_->Accept(share_target.id, [](auto status) { + std::cout << "Auto-accepted" << std::endl; + }); + break; + + case TransferMetadata::Status::kComplete: + std::cout << "Transfer completed!" << std::endl; + // Handle completed files from attachment_container + break; + + case TransferMetadata::Status::kFailed: + std::cout << "Transfer failed!" << std::endl; + break; + + default: + break; + } + } +}; +``` + +## Architecture + +### Service Initialization + +```cpp +NearbySharingServiceLinux service("DeviceName"); +``` + +The service initializes: +1. **Device Info**: Gets OS device name and type +2. **Bluetooth Adapter**: Checks BT availability +3. **Connections Core**: Sets up the Nearby Connections layer +4. **Service Controller Router**: Manages connection routing + +### Discovery Flow + +1. **Register Send Surface** (Foreground) +2. Service starts **scanning** for nearby devices +3. When device found: **OnShareTargetDiscovered** callback +4. Advertisement is **parsed** to extract device info +5. **ShareTarget** created with device details +6. User can **select target** and initiate transfer + +### Advertising Flow + +1. **Register Receive Surface** (Foreground/Background) +2. Service **builds advertisement** with device info +3. Service starts **advertising** via Bluetooth/Wi-Fi +4. When connection requested: **OnTransferUpdate** callback +5. User can **accept/reject** the incoming transfer + +### Transfer Flow + +#### Sending: +1. **SendAttachments()** with target ID and attachments +2. Service creates **connection request** +3. **Connection initiated** → Status: kConnecting +4. Connection **accepted** → Sends file/text payloads +5. **Payload transfer** → Status: kInProgress +6. **Transfer complete** → Status: kComplete + +#### Receiving: +1. **Incoming connection** → Status: kAwaitingLocalConfirmation +2. **Accept()** called → Connection accepted +3. **Receive payloads** → Status: kInProgress +4. **Payloads saved** to local storage +5. **Transfer complete** → Status: kComplete + +## Implementation Details + +### Advertisement Format + +The service uses a custom advertisement format: +- **Header byte**: Version, visibility, device type +- **Salt**: 2 random bytes +- **Metadata key**: 14 bytes (for encryption) +- **TLV fields**: Vendor ID, QR code, etc. +- **Device name** (optional): UTF-8 device name + +### Connection Strategy + +Uses `P2P_POINT_TO_POINT` strategy: +- Direct peer-to-peer connections +- Supports Bluetooth and Wi-Fi Direct +- Automatic medium selection based on availability + +### Medium Selection + +The service attempts to use available media: +1. **Bluetooth LE**: For discovery and initial connection +2. **Wi-Fi Direct**: For high-speed file transfer +3. **Wi-Fi LAN**: If devices on same network + +### Payload Types + +1. **BYTES**: For text content and metadata +2. **FILE**: For file transfers +3. **STREAM**: For real-time data + +## API Reference + +### Core Methods + +#### RegisterSendSurface +```cpp +void RegisterSendSurface( + TransferUpdateCallback* transfer_callback, + ShareTargetDiscoveredCallback* discovery_callback, + SendSurfaceState state, + Advertisement::BlockedVendorId blocked_vendor_id, + bool disable_wifi_hotspot, + std::function status_codes_callback); +``` + +#### RegisterReceiveSurface +```cpp +void RegisterReceiveSurface( + TransferUpdateCallback* transfer_callback, + ReceiveSurfaceState state, + Advertisement::BlockedVendorId vendor_id, + std::function status_codes_callback); +``` + +#### SendAttachments +```cpp +void SendAttachments( + int64_t share_target_id, + std::unique_ptr attachment_container, + std::function status_codes_callback); +``` + +#### Accept/Reject/Cancel +```cpp +void Accept(int64_t share_target_id, + std::function status_codes_callback); + +void Reject(int64_t share_target_id, + std::function status_codes_callback); + +void Cancel(int64_t share_target_id, + std::function status_codes_callback); +``` + +### Status Codes + +- **kOk**: Operation successful +- **kError**: General error +- **kOutOfOrderApiCall**: API called in wrong order +- **kTransferAlreadyInProgress**: Transfer already active +- **kNoAvailableConnectionMedium**: No BT/Wi-Fi available +- **kInvalidArgument**: Invalid parameter provided + +## Limitations + +Current implementation limitations: +- No settings persistence +- No contact management +- No certificate management +- No account integration +- Limited visibility control +- No Wi-Fi LAN detection +- No extended advertising support + +## Future Enhancements + +Potential improvements: +1. Add settings persistence (device name, visibility) +2. Implement contact management +3. Add certificate-based authentication +4. Support visibility time limits +5. Add Wi-Fi LAN connectivity detection +6. Implement file path updates during transfer +7. Add QR code generation for pairing +8. Support for extended advertising + +## Troubleshooting + +### Bluetooth Issues +```cpp +if (!service.IsBluetoothPresent()) { + std::cout << "Bluetooth adapter not found" << std::endl; +} +if (!service.IsBluetoothPowered()) { + std::cout << "Bluetooth is disabled" << std::endl; +} +``` + +### Discovery Not Working +- Ensure Bluetooth is enabled +- Check that sender is in foreground mode +- Verify receiver is advertising +- Check for Bluetooth permissions + +### Transfer Failures +- Verify file paths are accessible +- Check available disk space +- Ensure stable Bluetooth connection +- Monitor transfer callbacks for errors + +## License + +Copyright 2025 Google LLC. Licensed under Apache 2.0. diff --git a/sharing/linux/nearby_sharing_app.cc b/sharing/linux/nearby_sharing_app.cc new file mode 100644 index 00000000..828ea2c7 --- /dev/null +++ b/sharing/linux/nearby_sharing_app.cc @@ -0,0 +1,394 @@ +// Copyright 2025 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. + +#include +#include +#include +#include +#include +#include + +#include "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/attachment_container.h" +#include "sharing/file_attachment.h" +#include "sharing/text_attachment.h" +#include "sharing/share_target.h" +#include "sharing/transfer_metadata.h" +#include "sharing/transfer_update_callback.h" +#include "sharing/share_target_discovered_callback.h" +#include "internal/base/file_path.h" + +using namespace nearby::sharing; +using namespace nearby::sharing::linux; + +class MyTransferUpdateCallback : public TransferUpdateCallback { + public: + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + std::cout << "\n=== Transfer Update ===" << std::endl; + std::cout << "Device: " << share_target.device_name << std::endl; + std::cout << "Status: " << TransferMetadata::StatusToString(transfer_metadata.status()) << std::endl; + std::cout << "Progress: " << (transfer_metadata.progress() * 100) << "%" << std::endl; + std::cout << "Transferred: " << transfer_metadata.transferred_bytes() << " bytes" << std::endl; + std::cout << "Total attachments: " << transfer_metadata.total_attachments_count() << std::endl; + std::cout << "======================" << std::endl; + } +}; + +class MyShareTargetDiscoveredCallback : public ShareTargetDiscoveredCallback { + public: + void OnShareTargetDiscovered(const ShareTarget& share_target) override { + std::cout << "\n*** Device Discovered ***" << std::endl; + std::cout << "ID: " << share_target.id << std::endl; + std::cout << "Name: " << share_target.device_name << std::endl; + std::cout << "Vendor ID: " << static_cast(share_target.vendor_id) << std::endl; + std::cout << "*************************" << std::endl; + + discovered_targets_.push_back(share_target); + } + + void OnShareTargetLost(const ShareTarget& share_target) override { + std::cout << "\n*** Device Lost ***" << std::endl; + std::cout << "Name: " << share_target.device_name << std::endl; + std::cout << "*******************" << std::endl; + + for (auto it = discovered_targets_.begin(); it != discovered_targets_.end(); ++it) { + if (it->id == share_target.id) { + discovered_targets_.erase(it); + break; + } + } + } + + void OnShareTargetUpdated(const ShareTarget& share_target) override { + std::cout << "\n*** Device Updated ***" << std::endl; + std::cout << "Name: " << share_target.device_name << std::endl; + std::cout << "**********************" << std::endl; + } + + const std::vector& GetDiscoveredTargets() const { + return discovered_targets_; + } + + private: + std::vector discovered_targets_; +}; + +class NearbySharingApp { + public: + NearbySharingApp(const std::string& device_name) + : service_(std::make_unique(device_name)), + transfer_callback_(std::make_unique()), + discovery_callback_(std::make_unique()) { + std::cout << "Nearby Sharing Application initialized with device name: " + << device_name << std::endl; + } + + ~NearbySharingApp() { + Shutdown(); + } + + void StartAsReceiver() { + std::cout << "\n=== Starting as Receiver (Foreground) ===" << std::endl; + + service_->RegisterReceiveSurface( + transfer_callback_.get(), + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Successfully registered as receiver!" << std::endl; + } else { + std::cout << "Failed to register as receiver: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + std::cout << "Advertising enabled. Waiting for incoming connections..." << std::endl; + } + + void StartAsSender() { + std::cout << "\n=== Starting as Sender (Foreground) ===" << std::endl; + + service_->RegisterSendSurface( + transfer_callback_.get(), + discovery_callback_.get(), + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + false, // disable_wifi_hotspot + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Successfully registered as sender!" << std::endl; + } else { + std::cout << "Failed to register as sender: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + std::cout << "Scanning for nearby devices..." << std::endl; + } + + void SendFile(int64_t target_id, const std::string& file_path) { + std::cout << "\n=== Sending File ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + std::cout << "File: " << file_path << std::endl; + + auto attachment_container = std::make_unique(); + + attachment_container->AddFileAttachment(FileAttachment(nearby::FilePath(file_path))); + + service_->SendAttachments( + target_id, + std::move(attachment_container), + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "File send initiated successfully!" << std::endl; + } else { + std::cout << "Failed to send file: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void SendText(int64_t target_id, const std::string& text) { + std::cout << "\n=== Sending Text ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + std::cout << "Text: " << text << std::endl; + + auto attachment_container = std::make_unique(); + + attachment_container->AddTextAttachment(TextAttachment( + nearby::sharing::service::proto::TextMetadata::TEXT, + text, + std::nullopt, // text_title + std::nullopt // mime_type + )); + + service_->SendAttachments( + target_id, + std::move(attachment_container), + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Text send initiated successfully!" << std::endl; + } else { + std::cout << "Failed to send text: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void AcceptIncomingShare(int64_t target_id) { + std::cout << "\n=== Accepting Incoming Share ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + + service_->Accept( + target_id, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Share accepted!" << std::endl; + } else { + std::cout << "Failed to accept share: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void RejectIncomingShare(int64_t target_id) { + std::cout << "\n=== Rejecting Incoming Share ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + + service_->Reject( + target_id, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Share rejected!" << std::endl; + } else { + std::cout << "Failed to reject share: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void CancelTransfer(int64_t target_id) { + std::cout << "\n=== Canceling Transfer ===" << std::endl; + std::cout << "Target ID: " << target_id << std::endl; + + service_->Cancel( + target_id, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Transfer cancelled!" << std::endl; + } else { + std::cout << "Failed to cancel transfer: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + } + + void ListDiscoveredDevices() { + std::cout << "\n=== Discovered Devices ===" << std::endl; + const auto& targets = discovery_callback_->GetDiscoveredTargets(); + + if (targets.empty()) { + std::cout << "No devices found." << std::endl; + } else { + for (const auto& target : targets) { + std::cout << "ID: " << target.id + << " | Name: " << target.device_name + << " | Vendor: " << static_cast(target.vendor_id) << std::endl; + } + } + std::cout << "==========================" << std::endl; + } + + void PrintStatus() { + std::cout << "\n=== Service Status ===" << std::endl; + std::cout << "Bluetooth Present: " << (service_->IsBluetoothPresent() ? "Yes" : "No") << std::endl; + std::cout << "Bluetooth Powered: " << (service_->IsBluetoothPowered() ? "Yes" : "No") << std::endl; + std::cout << "Is Scanning: " << (service_->IsScanning() ? "Yes" : "No") << std::endl; + std::cout << "Is Transferring: " << (service_->IsTransferring() ? "Yes" : "No") << std::endl; + std::cout << "======================" << std::endl; + } + + void Shutdown() { + std::cout << "\n=== Shutting Down ===" << std::endl; + service_->Shutdown([](NearbySharingService::StatusCodes status) { + std::cout << "Shutdown complete: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + }); + } + + private: + std::unique_ptr service_; + std::unique_ptr transfer_callback_; + std::unique_ptr discovery_callback_; +}; + +void PrintMenu() { + std::cout << "\n========== Nearby Sharing Menu ==========" << std::endl; + std::cout << "1. Start as Receiver (advertise)" << std::endl; + std::cout << "2. Start as Sender (discover)" << std::endl; + std::cout << "3. List discovered devices" << std::endl; + std::cout << "4. Send file to device" << std::endl; + std::cout << "5. Send text to device" << std::endl; + std::cout << "6. Accept incoming share" << std::endl; + std::cout << "7. Reject incoming share" << std::endl; + std::cout << "8. Cancel transfer" << std::endl; + std::cout << "9. Print status" << std::endl; + std::cout << "0. Exit" << std::endl; + std::cout << "=========================================" << std::endl; + std::cout << "Enter choice: "; +} + +int main(int argc, char* argv[]) { + std::string device_name = "MyLinuxDevice"; + + if (argc > 1) { + device_name = argv[1]; + } + + std::cout << "========================================" << std::endl; + std::cout << " Nearby Sharing Linux Application" << std::endl; + std::cout << "========================================" << std::endl; + + NearbySharingApp app(device_name); + + bool running = true; + while (running) { + PrintMenu(); + + int choice; + std::cin >> choice; + + switch (choice) { + case 1: + app.StartAsReceiver(); + break; + + case 2: + app.StartAsSender(); + std::this_thread::sleep_for(std::chrono::seconds(2)); + app.ListDiscoveredDevices(); + break; + + case 3: + app.ListDiscoveredDevices(); + break; + + case 4: { + int64_t target_id; + std::string file_path; + std::cout << "Enter target ID: "; + std::cin >> target_id; + std::cout << "Enter file path: "; + std::cin.ignore(); + std::getline(std::cin, file_path); + app.SendFile(target_id, file_path); + break; + } + + case 5: { + int64_t target_id; + std::string text; + std::cout << "Enter target ID: "; + std::cin >> target_id; + std::cout << "Enter text to send: "; + std::cin.ignore(); + std::getline(std::cin, text); + app.SendText(target_id, text); + break; + } + + case 6: { + int64_t target_id; + std::cout << "Enter target ID to accept: "; + std::cin >> target_id; + app.AcceptIncomingShare(target_id); + break; + } + + case 7: { + int64_t target_id; + std::cout << "Enter target ID to reject: "; + std::cin >> target_id; + app.RejectIncomingShare(target_id); + break; + } + + case 8: { + int64_t target_id; + std::cout << "Enter target ID to cancel: "; + std::cin >> target_id; + app.CancelTransfer(target_id); + break; + } + + case 9: + app.PrintStatus(); + break; + + case 0: + running = false; + break; + + default: + std::cout << "Invalid choice. Please try again." << std::endl; + break; + } + } + + std::cout << "\nGoodbye!" << std::endl; + return 0; +} diff --git a/sharing/linux/nearby_sharing_app_BUILD b/sharing/linux/nearby_sharing_app_BUILD new file mode 100644 index 00000000..80252d43 --- /dev/null +++ b/sharing/linux/nearby_sharing_app_BUILD @@ -0,0 +1,28 @@ +# Copyright 2025 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. + +licenses(["notice"]) + +cc_binary( + name = "nearby_sharing_app", + srcs = ["nearby_sharing_app.cc"], + deps = [ + ":nearby_sharing_service_linux", + "//sharing:attachments", + "//sharing:types", + "//internal/platform:base", + "@com_google_absl//absl/strings", + ], + visibility = ["//visibility:public"], +) diff --git a/sharing/linux/simple_example.cc b/sharing/linux/simple_example.cc new file mode 100644 index 00000000..04a46151 --- /dev/null +++ b/sharing/linux/simple_example.cc @@ -0,0 +1,251 @@ +// Copyright 2025 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. + +// Simple example: Send a text message to nearby device +// +// Usage: +// Terminal 1 (Receiver): ./simple_receiver +// Terminal 2 (Sender): ./simple_sender "Hello World" + +#include +#include +#include +#include +#include + +#include "sharing/linux/nearby_sharing_service_linux.h" +#include "sharing/attachment_container.h" +#include "sharing/text_attachment.h" +#include "sharing/share_target.h" +#include "sharing/transfer_metadata.h" +#include "sharing/transfer_update_callback.h" +#include "sharing/share_target_discovered_callback.h" + +using namespace nearby::sharing; +using namespace nearby::sharing::linux; + +// Simple callback that automatically accepts incoming transfers +class SimpleReceiverCallback : public TransferUpdateCallback { + public: + explicit SimpleReceiverCallback(NearbySharingServiceLinux* service) + : service_(service) {} + + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + std::cout << "\n[Receiver] Transfer Update from: " << share_target.device_name << std::endl; + std::cout << "[Receiver] Status: " + << TransferMetadata::StatusToString(transfer_metadata.status()) << std::endl; + + // Auto-accept incoming transfers + if (transfer_metadata.status() == TransferMetadata::Status::kAwaitingLocalConfirmation) { + std::cout << "[Receiver] Auto-accepting transfer..." << std::endl; + service_->Accept(share_target.id, [](auto status) { + std::cout << "[Receiver] Accept status: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + }); + } + + // Show received content when complete + if (transfer_metadata.status() == TransferMetadata::Status::kComplete) { + std::cout << "\n[Receiver] ✓ Transfer Complete!" << std::endl; + + // Display received text + for (const auto& text : attachment_container.GetTextAttachments()) { + std::cout << "[Receiver] Received text: \"" << text.text_body() << "\"" << std::endl; + } + + // Display received files + for (const auto& file : attachment_container.GetFileAttachments()) { + std::cout << "[Receiver] Received file: " << file.file_name() << std::endl; + } + } + } + + private: + NearbySharingServiceLinux* service_; +}; + +// Callback for discovering nearby devices +class SimpleSenderCallback : public ShareTargetDiscoveredCallback { + public: + void OnShareTargetDiscovered(const ShareTarget& share_target) override { + std::cout << "\n[Sender] Found device: " << share_target.device_name + << " (ID: " << share_target.id << ")" << std::endl; + discovered_target_ = share_target; + has_target_ = true; + } + + void OnShareTargetLost(const ShareTarget& share_target) override { + std::cout << "[Sender] Lost device: " << share_target.device_name << std::endl; + if (has_target_ && discovered_target_.id == share_target.id) { + has_target_ = false; + } + } + + void OnShareTargetUpdated(const ShareTarget& share_target) override {} + + bool HasTarget() const { return has_target_; } + const ShareTarget& GetTarget() const { return discovered_target_; } + + private: + ShareTarget discovered_target_; + bool has_target_ = false; +}; + +// Transfer progress callback for sender +class SimpleSenderTransferCallback : public TransferUpdateCallback { + public: + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override { + std::cout << "[Sender] Transfer to " << share_target.device_name + << ": " << TransferMetadata::StatusToString(transfer_metadata.status()) + << " (" << (transfer_metadata.progress() * 100) << "%)" << std::endl; + + if (transfer_metadata.status() == TransferMetadata::Status::kComplete) { + std::cout << "[Sender] ✓ Transfer Complete!" << std::endl; + transfer_complete_ = true; + } else if (transfer_metadata.status() == TransferMetadata::Status::kFailed) { + std::cout << "[Sender] ✗ Transfer Failed!" << std::endl; + transfer_complete_ = true; + } + } + + bool IsTransferComplete() const { return transfer_complete_; } + + private: + bool transfer_complete_ = false; +}; + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cout << "Usage: " << argv[0] << " [message]" << std::endl; + std::cout << "Examples:" << std::endl; + std::cout << " " << argv[0] << " receiver # Start as receiver" << std::endl; + std::cout << " " << argv[0] << " sender \"Hello!\" # Send text message" << std::endl; + return 1; + } + + std::string mode = argv[1]; + + if (mode == "receiver") { + // ============= RECEIVER MODE ============= + std::cout << "=== Nearby Sharing Receiver ===" << std::endl; + + NearbySharingServiceLinux service("Receiver-Device"); + SimpleReceiverCallback callback(&service); + + // Register as a receiver (advertise to nearby devices) + service.RegisterReceiveSurface( + &callback, + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + [](auto status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "[Receiver] ✓ Advertising started" << std::endl; + std::cout << "[Receiver] Waiting for incoming transfers..." << std::endl; + } else { + std::cout << "[Receiver] ✗ Failed to start: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + // Keep running to receive transfers + std::cout << "\nPress Ctrl+C to exit..." << std::endl; + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + } else if (mode == "sender") { + // ============= SENDER MODE ============= + std::string message = "Hello from Nearby Sharing!"; + if (argc > 2) { + message = argv[2]; + } + + std::cout << "=== Nearby Sharing Sender ===" << std::endl; + std::cout << "[Sender] Message to send: \"" << message << "\"" << std::endl; + + NearbySharingServiceLinux service("Sender-Device"); + SimpleSenderCallback discovery_callback; + SimpleSenderTransferCallback transfer_callback; + + // Register as sender (scan for nearby devices) + service.RegisterSendSurface( + &transfer_callback, + &discovery_callback, + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + false, + [](auto status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "[Sender] ✓ Scanning started" << std::endl; + } else { + std::cout << "[Sender] ✗ Failed to start: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + } + }); + + // Wait for device discovery + std::cout << "[Sender] Scanning for nearby devices..." << std::endl; + int wait_time = 0; + while (!discovery_callback.HasTarget() && wait_time < 10) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + wait_time++; + std::cout << "." << std::flush; + } + std::cout << std::endl; + + if (!discovery_callback.HasTarget()) { + std::cout << "[Sender] ✗ No devices found. Make sure receiver is running!" << std::endl; + return 1; + } + + // Create text attachment + auto attachment_container = std::make_unique(); + attachment_container->AddTextAttachment(TextAttachment( + nearby::sharing::service::proto::TextMetadata::TEXT, + message, + std::nullopt, + std::nullopt + )); + + // Send to discovered device + const auto& target = discovery_callback.GetTarget(); + std::cout << "\n[Sender] Sending to: " << target.device_name << std::endl; + + service.SendAttachments( + target.id, + std::move(attachment_container), + [](auto status) { + std::cout << "[Sender] Send initiated: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + }); + + // Wait for transfer to complete + std::cout << "[Sender] Transferring..." << std::endl; + while (!transfer_callback.IsTransferComplete()) { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + std::cout << "\n[Sender] Done!" << std::endl; + + } else { + std::cout << "Invalid mode. Use 'receiver' or 'sender'" << std::endl; + return 1; + } + + return 0; +}