Files
mobile-android/Architecture.md

292 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`) и требует окончательного разрыва через интерфейсы.