Android DEX Documentation
A comprehensive guide to understanding the Android DEX codebase. This documentation covers the architecture, file structure, core services, and data flow that power the wireless desktop experience.
Flutter Desktop
Cross-platform app built with Flutter for Windows, macOS, and Linux
TCP Server
Real-time communication with Android devices over local network
scrcpy Integration
Display mirroring and virtual display creation via scrcpy
System Architecture
The Android DEX system consists of three main layers that work together to provide the desktop experience.
Presentation Layer
Flutter widgets handling user interface and interactions
Service Layer
Business logic handling connections and device control
External Tools
Command-line tools for Android communication
Connection Flow
Quick Start for Developers
Get the project running on your local machine in a few steps.
# Clone the repository
git clone https://github.com/Shrey113/Android-Dex.git
cd android_dex
# Install Flutter dependencies
flutter pub get
# Run the desktop app
flutter run -d windows
Prerequisites
- Flutter SDK 3.7.0 or higher
- Dart SDK (included with Flutter)
- Windows/macOS/Linux development environment
- ADB and scrcpy binaries in
All helper/folder
Project File Structure
Understanding the directory layout is key to navigating the codebase effectively.
Entry Point: main.dart
The application starts here. This file initializes the window manager, loads environment variables, starts the TCP server, and launches the Flutter app.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: ".env");
// Desktop window configuration
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(480, 720),
minimumSize: Size(400, 640),
center: true,
backgroundColor: Color(0xFF0D0D12),
titleBarStyle: TitleBarStyle.hidden,
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
}
// Initialize TCP server for Android communication
await AdbTcpServer.instance.init();
runApp(const AndroidDexApp());
}
- Load environment variables from
.envfile - Configure custom window frame (frameless with custom title bar)
- Start the singleton TCP server instance
- Launch the Flutter MaterialApp
App Configuration: app.dart
This file defines the MaterialApp widget and the dark theme used throughout the application.
class AndroidDexApp extends StatelessWidget {
const AndroidDexApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Android DEX',
debugShowCheckedModeBanner: false,
theme: _buildTheme(),
home: const HomeScreen(),
);
}
ThemeData _buildTheme() {
const primaryColor = Color(0xFF7C4DFF); // Purple accent
const surfaceColor = Color(0xFF0D0D12); // Dark background
const cardColor = Color(0xFF16161D); // Card background
// ... theme configuration
}
}
Color Palette
#7C4DFF
#00E5FF
#0D0D12
#16161D
#1DB954
#E53935
TCP Server: AdbTcpServer
The heart of the communication system. This singleton class manages TCP connections with Android devices, processes commands, and controls device features.
Singleton Pattern
Single instance accessible via AdbTcpServer.instance
Port 8456
Listens for incoming Android app connections
JSON Protocol
Line-delimited JSON messages for commands
Broadcast Support
Send messages to all connected clients
class AdbTcpServer {
static final AdbTcpServer instance = AdbTcpServer._internal();
AdbTcpServer._internal();
final int tcpPort = 8456;
final Map<String, Socket> connectedClients = {};
Future<bool> start() async {
if (_isRunning) return true;
try {
_server = await ServerSocket.bind(InternetAddress.anyIPv4, tcpPort);
_isRunning = true;
_statusController.add(true);
_listenForConnections();
await _startMdnsService();
return true;
} catch (e) {
return false;
}
}
}
Supported Commands
getBluetooth
getBluetooth
Get current Bluetooth status
toggleBluetooth
toggleBluetooth
Toggle Bluetooth on/off
getMobileData
getMobileData
Get mobile data status
toggleMobileData
toggleMobileData
Toggle mobile data on/off
req_app_audio=pkg
set_app_audio
Route specific app audio to PC
stop_all_audio
stop_all_audio
Stop all audio streaming
Scrcpy Service
Manages the scrcpy process for display mirroring and virtual display creation.
Future<(bool, String)> startScrcpy(String ip) async {
_process = await Process.start(scrcpyFullPath, [
'-s', ip, // Target device
'--new-display=1920x1080/280', // Virtual display
'--no-audio', // Audio handled separately
'--start-app=com.example.androiddex', // Launch DEX app
'--no-vd-system-decorations', // Clean desktop look
], workingDirectory: scrcpyDir);
_isRunning = true;
_statusController.add(true);
return (true, 'Display started');
}
The --new-display flag creates a virtual display on the Android device. This
requires scrcpy version 2.4 or higher with virtual display support.
AdbDevice Class
Data model representing a connected Android device:
class AdbDevice {
final String address; // e.g., "192.168.1.100:5555"
final String? product; // e.g., "CPH2447"
final String? model; // e.g., "CPH2447"
final String? device; // e.g., "OP594DL1"
final String? transportId; // ADB transport ID
String get displayName => model ?? device ?? address;
}
mDNS Discovery Service
A Python-based service that broadcasts the desktop server's presence on the local network, allowing Android devices to auto-discover it.
from zeroconf import Zeroconf, ServiceInfo
SERVICE_TYPE = "_httpapp._tcp.local."
HTTP_PORT = 5001
def register_mdns_service(zeroconf):
hostname, ip = get_network_info()
service_name = f"{ip.replace('.', '_')}_win_server._httpapp._tcp.local."
device_data = {
"status": "success",
"device_type": "windows",
"device_info": {
"hostname": hostname,
"ip": ip,
}
}
info = ServiceInfo(
SERVICE_TYPE,
service_name,
addresses=[socket.inet_aton(ip)],
port=HTTP_PORT,
properties={
b"host": hostname.encode(),
b"device_type": b"windows",
b"ip": ip.encode(),
},
)
zeroconf.register_service(info)
The mDNS service runs as a separate process, started by the Flutter app. It uses Zeroconf to
broadcast a service named _httpapp._tcp.local. containing the desktop's IP
address. Android apps on the same network can discover this service automatically.
Audio Process Manager
Manages per-app audio streaming from Android to the desktop using scrcpy's audio forwarding feature.
class SimpleAudioProcessManager {
static final SimpleAudioProcessManager instance = SimpleAudioProcessManager._();
Process? _proc;
String? _pkg; // Currently streaming package name
Future<bool> start(String pkg, List<String> args) async {
if (_pkg == pkg) return true; // Already streaming this app
// Stop existing audio stream
if (_proc != null) {
_proc!.kill();
_proc = null;
}
// Start new audio stream
_proc = await Process.start(args[0], args.sublist(1));
_pkg = pkg;
return true;
}
}
Home Screen UI
The main user interface containing the connection form, device list, and status indicators.
Component Structure
State Management
_isLoading
Connection in progress
_isConnected
Active DEX session
_connectedIp
Current device IP
_devices
List of discovered AdbDevice
_isServerRunning
TCP server status
Glass Card Widget
A reusable glassmorphism card component with blur effect and subtle borders.
class GlassCard extends StatelessWidget {
final Widget child;
final EdgeInsets padding;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(16),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
padding: padding,
decoration: BoxDecoration(
color: const Color(0xFF16161D).withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF2A2A35)),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.2),
blurRadius: 16,
offset: const Offset(0, 8),
),
],
),
child: child,
),
),
);
}
}
Connection Flow Diagram
Visual representation of the complete connection process from user action to active DEX session.
Command Protocol
The TCP communication uses a simple line-delimited JSON protocol.
Request Format
// Simple command (string)
getBluetooth\n
// Command with parameter
req_app_audio=com.spotify.music\n
// JSON command
{"type":"add_new_recent_app","package":"com.example.app"}\n
Response Format
// All responses are JSON
{"type":"getBluetooth","status":true}\n
{"type":"set_app_audio","app":"com.spotify.music","status":true}\n
{"type":"toggleMobileData","status":false}\n
State Management
The app uses Dart's built-in StreamController for reactive state updates.
// In Service class
final _statusController = StreamController<bool>.broadcast();
Stream<bool> get statusStream => _statusController.stream;
// Update state
_statusController.add(true);
// In UI (initState)
_serverStatusSubscription = AdbTcpServer.instance.statusStream.listen((isRunning) {
if (mounted) {
setState(() => _isServerRunning = isRunning);
}
});
- Services remain decoupled from UI
- Multiple listeners can subscribe
- Automatic cleanup with subscription disposal