Files
mobile-android/Architecture.md

11 KiB
Raw Blame History

Rosetta Android — Architecture

Документ отражает текущее состояние rosetta-android (ветка dev) по коду на 2026-04-18.

1. Архитектурный профиль

Приложение сейчас устроено как layered + service-oriented архитектура:

  • UI: MainActivity + Compose-экраны + ViewModel.
  • DI: Hilt (@HiltAndroidApp, @AndroidEntryPoint, модули в di/AppContainer.kt).
  • Runtime orchestration: ProtocolManager, CallManager, TransportManager, UpdateManager.
  • Session/Identity runtime state: SessionStore, SessionReducer, IdentityStore.
  • Data: MessageRepository, GroupRepository, AccountManager, PreferencesManager.
  • Persistence: Room (RosettaDatabase) + DataStore/SharedPreferences.

ProtocolManager остается крупным runtime orchestrator-объектом, но критичные зоны уже вынесены в отдельные сервисы.


2. Слои и границы

flowchart TB
    subgraph ENTRY["Android Entry Points"]
      E1["RosettaApplication"]
      E2["MainActivity"]
      E3["RosettaFirebaseMessagingService"]
      E4["IncomingCallActivity / CallForegroundService"]
    end

    subgraph DI["Hilt Singleton Graph"]
      D1["ProtocolGateway"]
      D2["SessionCoordinator"]
      D3["IdentityGateway"]
      D4["AccountManager / PreferencesManager"]
      D5["MessageRepository / GroupRepository"]
      D6["UiEntryPoint + UiDependencyAccess"]
    end

    subgraph SESSION["Session / Identity Runtime"]
      S1["SessionStore"]
      S2["SessionReducer"]
      S3["IdentityStore"]
      S4["AppSessionCoordinator"]
    end

    subgraph NET["Network Runtime"]
      N1["ProtocolManager"]
      N2["Protocol"]
      N3["PacketSubscriptionRegistry"]
      N4["ReadyPacketGate"]
    end

    subgraph DATA["Data + Persistence"]
      R1["MessageRepository"]
      R2["GroupRepository"]
      R3["Room: RosettaDatabase"]
    end

    ENTRY --> DI
    DI --> SESSION
    DI --> NET
    DI --> DATA
    SESSION --> NET
    DATA --> NET
    DATA --> R3
    NET --> N2

3. DI и composition root

3.1 Hilt

  • RosettaApplication помечен @HiltAndroidApp.
  • Entry points уровня Android-компонентов: MainActivity, IncomingCallActivity, CallForegroundService, RosettaFirebaseMessagingService.
  • Основные модули:
  • AppDataModule: AccountManager, PreferencesManager.
  • AppGatewayModule: биндинги ProtocolGateway, SessionCoordinator, IdentityGateway.

3.2 UI bridge для не-Hilt классов

Часть UI/VM пока получает зависимости через UiDependencyAccess -> UiEntryPoint. Это transitional-слой, чтобы не тащить singleton getInstance(...) в UI и постепенно перейти на чистый constructor injection.


4. Session lifecycle: единый source of truth

4.1 Модель состояния

SessionState:

  • LoggedOut
  • AuthInProgress(publicKey?, reason)
  • Ready(account, reason)

4.2 Модель событий

SessionAction:

  • LoggedOut
  • AuthInProgress
  • Ready
  • SyncFromCachedAccount

4.3 Контур изменения состояния

  • Только SessionStore владеет MutableStateFlow<SessionState>.
  • Только SessionReducer вычисляет next-state.
  • SessionCoordinator/AppSessionCoordinator больше не мутируют состояние напрямую, а делают dispatch(action).
  • SessionStore.dispatch(...) синхронно обновляет IdentityStore для консистентности account/profile/auth-runtime.
flowchart LR
    A["AuthFlow / MainActivity / Unlock / SetPassword"] --> B["SessionCoordinator.dispatch(action)"]
    B --> C["SessionStore.dispatch(action)"]
    C --> D["SessionReducer.reduce(current, action)"]
    D --> E["StateFlow<SessionState>"]
    C --> F["IdentityStore sync"]

4.4 State machine

stateDiagram-v2
    [*] --> LoggedOut
    LoggedOut --> AuthInProgress: dispatch(AuthInProgress)
    AuthInProgress --> Ready: dispatch(Ready)
    AuthInProgress --> LoggedOut: dispatch(LoggedOut)
    Ready --> LoggedOut: dispatch(LoggedOut)
    Ready --> Ready: dispatch(SyncFromCachedAccount(account))

5. Network orchestration после декомпозиции

ProtocolManager теперь делегирует отдельные зоны ответственности:

  • ConnectionOrchestrator: connect/reconnect/authenticate + network-aware поведение.
  • BootstrapCoordinator: пересчет lifecycle (AUTHENTICATED/BOOTSTRAPPING/READY) и работа с ReadyPacketGate.
  • PacketRouter: user/search cache + resolve/search continuation routing.
  • OwnProfileSyncService: применение собственного профиля из search и синхронизация IdentityStore.
  • RetryQueueService: retry очереди отправки PacketMessage.
  • PacketSubscriptionRegistry: централизованные подписки на пакеты и fan-out.
flowchart TB
    PM["ProtocolManager"] --> CO["ConnectionOrchestrator"]
    PM --> BC["BootstrapCoordinator"]
    PM --> PR["PacketRouter"]
    PM --> OPS["OwnProfileSyncService"]
    PM --> RQ["RetryQueueService"]
    PM --> PSR["PacketSubscriptionRegistry"]
    PM --> SUP["ProtocolConnectionSupervisor"]
    PM --> RPG["ReadyPacketGate"]
    PM --> P["Protocol (WebSocket + packet codec)"]

6. Централизация packet-subscriptions

Проблема дублирующихся low-level подписок закрыта через PacketSubscriptionRegistry:

  • На каждый packetId создается один bus и один bridge на Protocol.waitPacket(...).
  • Дальше packet fan-out идет в:
  • callback API (waitPacket/unwaitPacket),
  • SharedFlow (packetFlow(packetId)).
sequenceDiagram
    participant Feature as Feature/Service
    participant PM as ProtocolManager
    participant REG as PacketSubscriptionRegistry
    participant P as Protocol

    Feature->>PM: waitPacket(0x03, callback)
    PM->>REG: addCallback(0x03, callback)
    REG->>P: waitPacket(0x03, protocolBridge) [once per packetId]

    P-->>REG: Packet(0x03)
    REG-->>Feature: callback(packet)
    REG-->>Feature: packetFlow(0x03).emit(packet)

7. Отправка сообщений: use-cases + retry

Из ChatViewModel выделены use-cases:

  • SendTextMessageUseCase
  • SendMediaMessageUseCase
  • SendForwardUseCase

Текущий поток:

  1. ViewModel готовит command и шифрованный payload.
  2. UseCase собирает PacketMessage.
  3. UseCase вызывает protocolGateway.sendMessageWithRetry(packet).
  4. ProtocolManager регистрирует пакет в RetryQueueService и отправляет в сеть.
  5. Если lifecycle еще не READY, пакет попадает в ReadyPacketGate и flush после READY.
flowchart LR
    VM["ChatViewModel"] --> UC["Send*UseCase"]
    UC --> GW["ProtocolGateway.sendMessageWithRetry"]
    GW --> PM["ProtocolManager"]
    PM --> RQ["RetryQueueService"]
    PM --> RG["ReadyPacketGate"]
    PM --> P["Protocol.sendPacket"]

8. Auth/bootstrap: фактический runtime flow

sequenceDiagram
    participant UI as Auth UI (SetPassword/Unlock)
    participant SC as SessionCoordinatorImpl
    participant SS as SessionStore
    participant PG as ProtocolGateway
    participant PM as ProtocolManager
    participant AM as AccountManager

    UI->>SC: bootstrapAuthenticatedSession(account, reason)
    SC->>SS: dispatch(AuthInProgress)
    SC->>PG: initializeAccount(public, private)
    SC->>PG: connect()
    SC->>PG: authenticate(public, privateHash)
    SC->>PG: reconnectNowIfNeeded(...)
    SC->>AM: setCurrentAccount(public)
    SC->>SS: dispatch(Ready)

    PM-->>PM: HANDSHAKE -> AUTHENTICATED -> BOOTSTRAPPING
    PM-->>PM: SyncCompleted + OwnProfileResolved
    PM-->>PM: connectionLifecycleState = READY

Важно: SessionState.Ready (app-session готова) и connectionLifecycleState = READY (сеть готова) — это разные state-модели.


9. Состояния соединения (network lifecycle)

ProtocolManager.connectionLifecycleState:

  • DISCONNECTED
  • CONNECTING
  • HANDSHAKING
  • AUTHENTICATED
  • BOOTSTRAPPING
  • READY
  • DEVICE_VERIFICATION_REQUIRED
stateDiagram-v2
    [*] --> DISCONNECTED
    DISCONNECTED --> CONNECTING
    CONNECTING --> HANDSHAKING
    HANDSHAKING --> DEVICE_VERIFICATION_REQUIRED
    HANDSHAKING --> AUTHENTICATED
    AUTHENTICATED --> BOOTSTRAPPING
    BOOTSTRAPPING --> READY
    READY --> DISCONNECTED
    DEVICE_VERIFICATION_REQUIRED --> CONNECTING

10. Ключевые файлы новой архитектуры

  • app/src/main/java/com/rosetta/messenger/di/AppContainer.kt
  • app/src/main/java/com/rosetta/messenger/di/UiEntryPoint.kt
  • app/src/main/java/com/rosetta/messenger/session/AppSessionCoordinator.kt
  • app/src/main/java/com/rosetta/messenger/session/SessionStore.kt
  • app/src/main/java/com/rosetta/messenger/session/SessionReducer.kt
  • app/src/main/java/com/rosetta/messenger/session/SessionAction.kt
  • app/src/main/java/com/rosetta/messenger/session/IdentityStore.kt
  • app/src/main/java/com/rosetta/messenger/network/ProtocolManager.kt
  • app/src/main/java/com/rosetta/messenger/network/PacketSubscriptionRegistry.kt
  • app/src/main/java/com/rosetta/messenger/network/ProtocolConnectionModels.kt
  • app/src/main/java/com/rosetta/messenger/network/ProtocolConnectionSupervisor.kt
  • app/src/main/java/com/rosetta/messenger/network/ReadyPacketGate.kt
  • app/src/main/java/com/rosetta/messenger/network/connection/ConnectionOrchestrator.kt
  • app/src/main/java/com/rosetta/messenger/network/connection/BootstrapCoordinator.kt
  • app/src/main/java/com/rosetta/messenger/network/connection/PacketRouter.kt
  • app/src/main/java/com/rosetta/messenger/network/connection/OwnProfileSyncService.kt
  • app/src/main/java/com/rosetta/messenger/network/connection/RetryQueueService.kt
  • app/src/main/java/com/rosetta/messenger/ui/chats/usecase/SendTextMessageUseCase.kt
  • app/src/main/java/com/rosetta/messenger/ui/chats/usecase/SendMediaMessageUseCase.kt
  • app/src/main/java/com/rosetta/messenger/ui/chats/usecase/SendForwardUseCase.kt

11. Что осталось как технический долг

  • ProtocolManager все еще содержит много cross-cutting логики и требует дальнейшей декомпозиции.
  • Не весь UI перешел на constructor injection (UiDependencyAccess пока нужен для части ViewModel).
  • Часть data-слоя напрямую знает о network singleton (ProtocolManager) и требует окончательного разрыва через интерфейсы.