292 lines
11 KiB
Markdown
292 lines
11 KiB
Markdown
# 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. Слои и границы
|
||
|
||
```mermaid
|
||
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.
|
||
|
||
```mermaid
|
||
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
|
||
|
||
```mermaid
|
||
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.
|
||
|
||
```mermaid
|
||
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)`).
|
||
|
||
```mermaid
|
||
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`.
|
||
|
||
```mermaid
|
||
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
|
||
|
||
```mermaid
|
||
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`
|
||
|
||
```mermaid
|
||
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`) и требует окончательного разрыва через интерфейсы.
|