Files
god_eye/docs/TECHNICAL_SPECIFICATION_ANDROID.md
2025-10-04 11:55:55 +09:00

20 KiB
Raw Blame History

Техническое задание и промпт для создания Android приложения GodEye Signal Center

🎯 ТЕХНИЧЕСКОЕ ЗАДАНИЕ

Назначение системы

Создать Android приложение для системы удаленного доступа к камерам смартфона "GodEye Signal Center". Приложение должно предоставлять операторам доступ к камерам устройства через WebRTC с возможностью переключения между различными типами камер.

Архитектура системы

[Android App] ←→ [WebSocket/Socket.IO] ←→ [Backend Server] ←→ [Desktop Operator]
             ←→ [WebRTC P2P Connection] ←→ [Desktop Operator]

Основные компоненты

  1. MainActivity - главный экран с управлением подключением
  2. SocketService - сервис для WebSocket соединения с backend
  3. CameraManager - управление камерами устройства (Camera2 API)
  4. WebRTCManager - обработка WebRTC соединений для видеопотока
  5. PermissionManager - управление разрешениями приложения
  6. SessionManager - управление активными сессиями с операторами

Функциональные требования

  • Подключение к backend серверу по WebSocket (Socket.IO)
  • Регистрация устройства с передачей характеристик
  • Получение и обработка запросов доступа к камере от операторов
  • Диалоги согласия пользователя на доступ к камере
  • WebRTC соединение для передачи видеопотока
  • Переключение между камерами: основная, фронтальная, широкоугольная, телеобъектив
  • Отображение активных сессий и их управление
  • Автоматическое переподключение при обрыве соединения
  • Работа в фоне (Foreground Service)
  • Уведомления о статусе подключения

Технические требования

  • Платформа: Android API 24+ (Android 7.0+)
  • Язык: Kotlin
  • Архитектура: MVVM с LiveData
  • Сеть: Socket.IO для сигнализации, WebRTC для медиа
  • Камера: Camera2 API для работы с камерами
  • UI: Material Design 3
  • Разрешения: CAMERA, RECORD_AUDIO, INTERNET, FOREGROUND_SERVICE

📋 ПОЛНЫЙ ПРОМПТ ДЛЯ COPILOT


Создай полное Android приложение на Kotlin для системы GodEye Signal Center со следующими требованиями:

🚀 ОСНОВНАЯ ЗАДАЧА

Разработать Android приложение, которое подключается к backend серверу, получает запросы от операторов на доступ к камере устройства и предоставляет видеопоток через WebRTC с возможностью переключения между камерами.

📱 СТРУКТУРА ПРОЕКТА

app/
├── src/main/
│   ├── java/com/godeye/android/
│   │   ├── MainActivity.kt
│   │   ├── services/
│   │   │   ├── SocketService.kt
│   │   │   └── CameraService.kt
│   │   ├── managers/
│   │   │   ├── CameraManager.kt
│   │   │   ├── WebRTCManager.kt
│   │   │   ├── SessionManager.kt
│   │   │   └── PermissionManager.kt
│   │   ├── models/
│   │   │   ├── DeviceInfo.kt
│   │   │   ├── CameraSession.kt
│   │   │   └── SocketEvents.kt
│   │   ├── ui/
│   │   │   ├── dialogs/
│   │   │   │   └── CameraRequestDialog.kt
│   │   │   └── adapters/
│   │   │       └── SessionAdapter.kt
│   │   └── utils/
│   │       ├── Constants.kt
│   │       └── Extensions.kt
│   ├── res/
│   │   ├── layout/
│   │   │   ├── activity_main.xml
│   │   │   ├── dialog_camera_request.xml
│   │   │   └── item_session.xml
│   │   ├── values/
│   │   │   ├── strings.xml
│   │   │   ├── colors.xml
│   │   │   └── themes.xml
│   │   └── drawable/
│   └── AndroidManifest.xml
└── build.gradle (app)

🔧 BACKEND API ENDPOINTS

WebSocket События (Socket.IO на порту 3001):

// Регистрация устройства
socket.emit("register:android", JsonObject().apply {
    addProperty("deviceId", deviceId)
    add("deviceInfo", JsonObject().apply {
        addProperty("model", Build.MODEL)
        addProperty("androidVersion", Build.VERSION.RELEASE)
        addProperty("appVersion", "1.0.0")
        add("availableCameras", JsonArray().apply {
            add("back"); add("front"); add("wide"); add("telephoto")
        })
    })
})

// Обработка входящих событий
socket.on("register:success") { /* успешная регистрация */ }
socket.on("camera:request") { /* запрос доступа к камере */ }
socket.on("camera:disconnect") { /* завершение сессии */ }
socket.on("camera:switch") { /* переключение камеры */ }
socket.on("webrtc:offer") { /* WebRTC offer от оператора */ }
socket.on("webrtc:ice-candidate") { /* ICE candidates */ }

// Отправка ответов
socket.emit("camera:response", JsonObject().apply {
    addProperty("sessionId", sessionId)
    addProperty("accepted", true)
})
socket.emit("webrtc:answer", JsonObject().apply {
    addProperty("sessionId", sessionId)
    add("answer", /* RTCSessionDescription */)
})

🎨 UI/UX ТРЕБОВАНИЯ

MainActivity.xml:

<!-- Основной экран с полями: -->
- TextView: Device ID (автогенерируемый)
- EditText: Server URL (по умолчанию: http://192.168.1.100:3001)
- TextView: Connection Status
- Button: Connect/Disconnect
- RecyclerView: Active Sessions
- ProgressBar: Connection progress
- FloatingActionButton: Settings

CameraRequestDialog.xml:

<!-- Диалог запроса доступа: -->
- ImageView: Operator avatar/icon
- TextView: "Оператор {operatorId} запрашивает доступ к камере {cameraType}"
- TextView: Session ID
- Button: "Разрешить" / "Отклонить"
- CheckBox: "Запомнить для этого оператора"

💾 DATA MODELS

data class DeviceInfo(
    val model: String,
    val androidVersion: String,
    val appVersion: String,
    val availableCameras: List<String>
)

data class CameraSession(
    val sessionId: String,
    val operatorId: String,
    val cameraType: String,
    val startTime: Long,
    var isActive: Boolean = true,
    var webRTCConnected: Boolean = false
)

sealed class SocketEvent {
    data class RegisterAndroid(val deviceId: String, val deviceInfo: DeviceInfo) : SocketEvent()
    data class CameraRequest(val sessionId: String, val operatorId: String, val cameraType: String) : SocketEvent()
    data class CameraResponse(val sessionId: String, val accepted: Boolean) : SocketEvent()
    data class WebRTCOffer(val sessionId: String, val offer: String) : SocketEvent()
    data class WebRTCAnswer(val sessionId: String, val answer: String) : SocketEvent()
}

🔐 РАЗРЕШЕНИЯ (AndroidManifest.xml):

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />

<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.microphone" android:required="true" />

📚 DEPENDENCIES (build.gradle):

dependencies {
    implementation 'androidx.core:core-ktx:1.12.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
    implementation 'androidx.activity:activity-ktx:1.8.2'
    
    // Socket.IO для WebSocket соединения
    implementation 'io.socket:socket.io-client:2.1.0'
    
    // WebRTC для видеопотока
    implementation 'org.webrtc:google-webrtc:1.0.32006'
    
    // Camera2 API
    implementation 'androidx.camera:camera-core:1.3.1'
    implementation 'androidx.camera:camera-camera2:1.3.1'
    implementation 'androidx.camera:camera-lifecycle:1.3.1'
    implementation 'androidx.camera:camera-view:1.3.1'
    
    // JSON парсинг
    implementation 'com.google.code.gson:gson:2.10.1'
    
    // Корутины
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
    
    // RecyclerView
    implementation 'androidx.recyclerview:recyclerview:1.3.2'
    
    // Work Manager для фоновых задач
    implementation 'androidx.work:work-runtime-ktx:2.9.0'
}

🏗️ ОСНОВНЫЕ КЛАССЫ

1. MainActivity.kt - Основная логика:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var socketService: SocketService
    private lateinit var sessionAdapter: SessionAdapter
    private val viewModel: MainViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // Инициализация UI
        // Проверка разрешений
        // Настройка RecyclerView для сессий
        // Подключение к SocketService
    }
    
    private fun connectToServer() {
        val serverUrl = binding.etServerUrl.text.toString()
        socketService.connect(serverUrl)
    }
    
    private fun showCameraRequestDialog(request: CameraRequest) {
        CameraRequestDialog.newInstance(request).show(supportFragmentManager, "camera_request")
    }
}

2. SocketService.kt - WebSocket соединение:

class SocketService : Service() {
    private lateinit var socket: Socket
    private val binder = LocalBinder()
    
    fun connect(serverUrl: String) {
        socket = IO.socket(serverUrl)
        socket.connect()
        registerDevice()
        setupEventListeners()
    }
    
    private fun registerDevice() {
        val deviceInfo = DeviceInfo(
            model = Build.MODEL,
            androidVersion = Build.VERSION.RELEASE,
            appVersion = BuildConfig.VERSION_NAME,
            availableCameras = CameraManager.getAvailableCameraTypes()
        )
        socket.emit("register:android", gson.toJson(RegisterAndroid(deviceId, deviceInfo)))
    }
    
    private fun setupEventListeners() {
        socket.on("camera:request") { args ->
            val request = gson.fromJson(args[0].toString(), CameraRequest::class.java)
            // Отправить broadcast для показа диалога
        }
    }
}

3. CameraManager.kt - Управление камерами:

class CameraManager(private val context: Context) {
    private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as android.hardware.camera2.CameraManager
    private var currentCameraId: String? = null
    private var captureSession: CameraCaptureSession? = null
    
    fun getAvailableCameraTypes(): List<String> {
        val cameras = mutableListOf<String>()
        try {
            for (cameraId in cameraManager.cameraIdList) {
                val characteristics = cameraManager.getCameraCharacteristics(cameraId)
                val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
                when (facing) {
                    CameraCharacteristics.LENS_FACING_BACK -> cameras.add("back")
                    CameraCharacteristics.LENS_FACING_FRONT -> cameras.add("front")
                }
                // Проверка на wide и telephoto
            }
        } catch (e: Exception) {
            Log.e("CameraManager", "Error getting cameras", e)
        }
        return cameras
    }
    
    @SuppressLint("MissingPermission")
    fun startCamera(cameraType: String, surface: Surface) {
        val cameraId = getCameraIdForType(cameraType) ?: return
        cameraManager.openCamera(cameraId, cameraStateCallback, null)
    }
}

4. WebRTCManager.kt - WebRTC соединения:

class WebRTCManager(private val context: Context) {
    private lateinit var peerConnectionFactory: PeerConnectionFactory
    private var peerConnection: RTCPeerConnection? = null
    private var localVideoSource: VideoSource? = null
    
    fun initialize() {
        val initializationOptions = PeerConnectionFactory.InitializationOptions.builder(context)
            .setEnableInternalTracer(false)
            .createInitializationOptions()
        PeerConnectionFactory.initialize(initializationOptions)
        
        val options = PeerConnectionFactory.Options()
        peerConnectionFactory = PeerConnectionFactory.builder()
            .setOptions(options)
            .createPeerConnectionFactory()
    }
    
    fun createOffer(sessionId: String) {
        val mediaConstraints = MediaConstraints()
        peerConnection?.createOffer(object : SdpObserver {
            override fun onCreateSuccess(sessionDescription: SessionDescription) {
                // Отправить offer через WebSocket
            }
        }, mediaConstraints)
    }
    
    fun handleAnswer(sessionId: String, answer: SessionDescription) {
        peerConnection?.setRemoteDescription(SimpleSdpObserver(), answer)
    }
}

🔄 WORKFLOW ПРИЛОЖЕНИЯ

  1. Запуск приложения:

    • Проверка разрешений (CAMERA, AUDIO, INTERNET)
    • Генерация уникального Device ID
    • Определение доступных камер устройства
    • Инициализация WebRTC
  2. Подключение к серверу:

    • Подключение Socket.IO к указанному URL
    • Регистрация устройства с отправкой характеристик
    • Ожидание подтверждения регистрации
    • Запуск Foreground Service для фоновой работы
  3. Обработка запроса камеры:

    • Получение события "camera:request" от сервера
    • Показ диалога пользователю с информацией об операторе
    • При согласии - отправка "camera:response" с accepted: true
    • Инициализация WebRTC соединения
  4. WebRTC соединение:

    • Создание RTCPeerConnection
    • Настройка локального видеопотока с указанной камеры
    • Обмен offer/answer/ice-candidates с оператором
    • Начало передачи видеопотока
  5. Управление сессией:

    • Отображение активных сессий в RecyclerView
    • Обработка команд переключения камеры
    • Завершение сессии по команде оператора или пользователя
    • Очистка ресурсов WebRTC

⚙️ НАСТРОЙКИ И КОНФИГУРАЦИЯ

SharedPreferences ключи:

object PreferenceKeys {
    const val SERVER_URL = "server_url"
    const val DEVICE_ID = "device_id"
    const val AUTO_ACCEPT_REQUESTS = "auto_accept_requests"
    const val CAMERA_QUALITY = "camera_quality"
    const val NOTIFICATION_ENABLED = "notification_enabled"
}

Настройки WebRTC:

val iceServers = listOf(
    RTCIceServer.builder("stun:stun.l.google.com:19302").build(),
    RTCIceServer.builder("stun:stun1.l.google.com:19302").build()
)

val rtcConfig = RTCConfiguration(iceServers).apply {
    tcpCandidatePolicy = RTCConfiguration.TcpCandidatePolicy.DISABLED
    bundlePolicy = RTCConfiguration.BundlePolicy.MAXBUNDLE
    rtcpMuxPolicy = RTCConfiguration.RtcpMuxPolicy.REQUIRE
}

🐛 ОБРАБОТКА ОШИБОК

sealed class AppError {
    object NetworkError : AppError()
    object CameraPermissionDenied : AppError()
    object CameraNotAvailable : AppError()
    object WebRTCConnectionFailed : AppError()
    data class SocketError(val message: String) : AppError()
    data class UnknownError(val throwable: Throwable) : AppError()
}

class ErrorHandler {
    fun handleError(error: AppError, context: Context) {
        when (error) {
            is AppError.NetworkError -> showNetworkError(context)
            is AppError.CameraPermissionDenied -> showPermissionDialog(context)
            is AppError.WebRTCConnectionFailed -> restartWebRTC()
            // и т.д.
        }
    }
}

📋 ТЕСТИРОВАНИЕ

  1. Запуск backend сервера: cd backend && node src/server.js
  2. Веб-демо оператора: http://localhost:3001
  3. Подключение Android: указать URL http://192.168.1.100:3001
  4. Тестовые сценарии:
    • Регистрация устройства
    • Запрос доступа к камере от веб-демо
    • Переключение между камерами
    • Разрыв и восстановление соединения

🚀 ФИНАЛЬНЫЕ ТРЕБОВАНИЯ

Создай полный рабочий проект со всеми перечисленными файлами, который:

  • Компилируется без ошибок на API 24+
  • Корректно подключается к backend серверу
  • Отображает запросы операторов в диалогах
  • Передает видеопоток через WebRTC
  • Поддерживает переключение камер
  • Работает в фоне с уведомлениями
  • Имеет современный Material Design UI
  • Обрабатывает все ошибки и исключения

Backend server URL для тестирования: http://localhost:3001 или http://192.168.1.100:3001


📝 ДОПОЛНИТЕЛЬНЫЕ ПРИМЕЧАНИЯ

  • Используй современный Kotlin синтаксис с корутинами
  • Применяй MVVM архитектуру с LiveData/StateFlow
  • Все строки выноси в strings.xml с поддержкой русского языка
  • Добавь логирование всех важных событий
  • Используй Material Design 3 компоненты
  • Обеспечь backward compatibility с API 24
  • Добавь комментарии к сложной логике WebRTC и Camera2

Этот промпт содержит все необходимые технические детали для создания полнофункционального Android приложения GodEye Signal Center!