zai-mcp-server
Version:
🚀 REVOLUTIONARY AI-to-AI Collaboration Platform v6.1! NEW: Advanced Debugging Tools with Screenshot Analysis, Console Error Parsing, Automated Fix Generation, 5 Specialized Debugging Agents, Visual UI Analysis, JavaScript Error Intelligence, CSS/HTML Fix
776 lines (668 loc) • 27.7 kB
JavaScript
/**
* Android Debugging Manager - Comprehensive Android app debugging system
* Supports wireless debugging, screenshot analysis, logcat monitoring, and automated fixes
*/
import { spawn, exec } from 'child_process';
import { promisify } from 'util';
import fs from 'fs/promises';
import path from 'path';
const execAsync = promisify(exec);
export class AndroidDebuggingManager {
constructor(multiProviderAI, debuggingOrchestrator) {
this.multiProviderAI = multiProviderAI;
this.debuggingOrchestrator = debuggingOrchestrator;
this.connectedDevices = new Map();
this.activeDebuggingSessions = new Map();
this.logcatProcesses = new Map();
this.screenshotCache = new Map();
this.batteryAnalyzers = new Map();
this.performanceMonitors = new Map();
this.multiDevicePool = new Set();
this.deviceFarmConnections = new Map();
// Enhanced debugging capabilities
this.supportedLanguages = ['java', 'kotlin', 'xml', 'javascript', 'dart'];
this.debuggingModes = ['wireless', 'usb', 'emulator', 'cloud'];
this.performanceMetrics = ['cpu', 'memory', 'battery', 'network', 'gpu'];
// Android debugging configuration
this.adbPath = this.findAdbPath();
this.screenshotDir = './android-screenshots';
this.logcatDir = './android-logs';
this.performanceDir = './android-performance';
this.batteryDir = './android-battery';
// Initialize directories
this.initializeDirectories();
console.log('📱 Enhanced Android Debugging Manager initialized with multi-device and cloud support');
}
/**
* Find ADB path on the system
*/
findAdbPath() {
const possiblePaths = [
'/usr/local/bin/adb',
'/usr/bin/adb',
process.env.ANDROID_HOME ? `${process.env.ANDROID_HOME}/platform-tools/adb` : null,
process.env.ANDROID_SDK_ROOT ? `${process.env.ANDROID_SDK_ROOT}/platform-tools/adb` : null,
'adb' // Assume it's in PATH
].filter(Boolean);
return possiblePaths[0] || 'adb';
}
/**
* Initialize required directories
*/
async initializeDirectories() {
try {
await fs.mkdir(this.screenshotDir, { recursive: true });
await fs.mkdir(this.logcatDir, { recursive: true });
await fs.mkdir(this.performanceDir, { recursive: true });
await fs.mkdir(this.batteryDir, { recursive: true });
console.log('📁 Enhanced Android debugging directories initialized');
} catch (error) {
console.error('Failed to initialize directories:', error.message);
}
}
/**
* Connect to Android device via wireless debugging
*/
async connectWirelessDevice(deviceIp, port = 5555) {
try {
console.log(`📱 Connecting to Android device at ${deviceIp}:${port}...`);
// Enable wireless debugging
const connectResult = await execAsync(`${this.adbPath} connect ${deviceIp}:${port}`);
if (connectResult.stdout.includes('connected')) {
const deviceId = `${deviceIp}:${port}`;
// Get device info
const deviceInfo = await this.getDeviceInfo(deviceId);
this.connectedDevices.set(deviceId, {
id: deviceId,
ip: deviceIp,
port,
connected: true,
info: deviceInfo,
connectedAt: Date.now()
});
console.log(`✅ Connected to Android device: ${deviceInfo.model} (${deviceInfo.version})`);
return {
success: true,
deviceId,
deviceInfo,
message: `Connected to ${deviceInfo.model}`
};
} else {
throw new Error('Failed to connect to device');
}
} catch (error) {
console.error(`❌ Failed to connect to ${deviceIp}:${port}:`, error.message);
return {
success: false,
error: error.message,
suggestions: [
'Ensure wireless debugging is enabled on the device',
'Check that the device is on the same network',
'Verify the IP address and port are correct',
'Try pairing the device first if using Android 11+'
]
};
}
}
/**
* Pair with Android device (Android 11+)
*/
async pairDevice(deviceIp, pairingPort, pairingCode) {
try {
console.log(`🔗 Pairing with Android device at ${deviceIp}:${pairingPort}...`);
const pairResult = await execAsync(`${this.adbPath} pair ${deviceIp}:${pairingPort} ${pairingCode}`);
if (pairResult.stdout.includes('Successfully paired')) {
console.log('✅ Device paired successfully');
return {
success: true,
message: 'Device paired successfully. You can now connect.'
};
} else {
throw new Error('Pairing failed');
}
} catch (error) {
console.error('❌ Device pairing failed:', error.message);
return {
success: false,
error: error.message,
suggestions: [
'Verify the pairing code is correct',
'Check the pairing port number',
'Ensure the pairing dialog is still open on the device'
]
};
}
}
/**
* Get device information
*/
async getDeviceInfo(deviceId) {
try {
const [model, version, sdk, manufacturer] = await Promise.all([
execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.product.model`),
execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.build.version.release`),
execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.build.version.sdk`),
execAsync(`${this.adbPath} -s ${deviceId} shell getprop ro.product.manufacturer`)
]);
return {
model: model.stdout.trim(),
version: version.stdout.trim(),
sdk: parseInt(sdk.stdout.trim()),
manufacturer: manufacturer.stdout.trim()
};
} catch (error) {
console.error('Failed to get device info:', error.message);
return {
model: 'Unknown',
version: 'Unknown',
sdk: 0,
manufacturer: 'Unknown'
};
}
}
/**
* List connected Android devices
*/
async listConnectedDevices() {
try {
const result = await execAsync(`${this.adbPath} devices`);
const lines = result.stdout.split('\n').slice(1); // Skip header
const devices = [];
for (const line of lines) {
if (line.trim() && line.includes('device')) {
const deviceId = line.split('\t')[0];
const deviceInfo = await this.getDeviceInfo(deviceId);
devices.push({
id: deviceId,
status: 'connected',
info: deviceInfo
});
}
}
return devices;
} catch (error) {
console.error('Failed to list devices:', error.message);
return [];
}
}
/**
* Take screenshot of Android device
*/
async takeScreenshot(deviceId, options = {}) {
try {
console.log(`📸 Taking screenshot of device ${deviceId}...`);
const timestamp = Date.now();
const filename = `screenshot_${deviceId.replace(':', '_')}_${timestamp}.png`;
const localPath = path.join(this.screenshotDir, filename);
const devicePath = '/sdcard/screenshot_temp.png';
// Take screenshot on device
await execAsync(`${this.adbPath} -s ${deviceId} shell screencap -p ${devicePath}`);
// Pull screenshot to local machine
await execAsync(`${this.adbPath} -s ${deviceId} pull ${devicePath} ${localPath}`);
// Clean up device
await execAsync(`${this.adbPath} -s ${deviceId} shell rm ${devicePath}`);
// Read screenshot data
const screenshotData = await fs.readFile(localPath);
const base64Data = `data:image/png;base64,${screenshotData.toString('base64')}`;
// Cache screenshot
this.screenshotCache.set(`${deviceId}_${timestamp}`, {
path: localPath,
data: base64Data,
timestamp,
deviceId
});
console.log(`✅ Screenshot saved: ${filename}`);
return {
success: true,
filename,
path: localPath,
data: base64Data,
timestamp,
deviceId
};
} catch (error) {
console.error(`❌ Failed to take screenshot:`, error.message);
return {
success: false,
error: error.message
};
}
}
/**
* Start logcat monitoring
*/
async startLogcatMonitoring(deviceId, options = {}) {
try {
console.log(`📋 Starting logcat monitoring for device ${deviceId}...`);
const logFile = path.join(this.logcatDir, `logcat_${deviceId.replace(':', '_')}_${Date.now()}.log`);
const logStream = await fs.open(logFile, 'w');
// Clear existing logs if requested
if (options.clearLogs) {
await execAsync(`${this.adbPath} -s ${deviceId} logcat -c`);
}
// Start logcat process
const logcatArgs = ['-s', deviceId, 'logcat'];
// Add filters if specified
if (options.tags) {
logcatArgs.push(...options.tags.map(tag => `${tag}:V`));
}
if (options.priority) {
logcatArgs.push(`*:${options.priority}`);
}
const logcatProcess = spawn(this.adbPath, logcatArgs);
// Store process reference
this.logcatProcesses.set(deviceId, {
process: logcatProcess,
logFile,
startTime: Date.now(),
options
});
// Handle logcat output
logcatProcess.stdout.on('data', async (data) => {
const logEntry = data.toString();
await logStream.write(logEntry);
// Emit real-time log events if needed
this.emit('logcat', {
deviceId,
timestamp: Date.now(),
data: logEntry
});
});
logcatProcess.stderr.on('data', (data) => {
console.error(`Logcat error: ${data}`);
});
logcatProcess.on('close', (code) => {
console.log(`📋 Logcat monitoring stopped for ${deviceId} (code: ${code})`);
logStream.close();
this.logcatProcesses.delete(deviceId);
});
console.log(`✅ Logcat monitoring started for ${deviceId}`);
return {
success: true,
deviceId,
logFile,
message: 'Logcat monitoring started'
};
} catch (error) {
console.error(`❌ Failed to start logcat monitoring:`, error.message);
return {
success: false,
error: error.message
};
}
}
/**
* Stop logcat monitoring
*/
async stopLogcatMonitoring(deviceId) {
try {
const logcatInfo = this.logcatProcesses.get(deviceId);
if (logcatInfo) {
logcatInfo.process.kill();
this.logcatProcesses.delete(deviceId);
console.log(`✅ Logcat monitoring stopped for ${deviceId}`);
return {
success: true,
message: 'Logcat monitoring stopped'
};
} else {
return {
success: false,
error: 'No active logcat monitoring found for this device'
};
}
} catch (error) {
console.error(`❌ Failed to stop logcat monitoring:`, error.message);
return {
success: false,
error: error.message
};
}
}
/**
* Analyze Android logcat for errors
*/
async analyzeLogcat(deviceId, options = {}) {
try {
console.log(`🔍 Analyzing logcat for device ${deviceId}...`);
// Get recent logcat entries
const logcatResult = await execAsync(
`${this.adbPath} -s ${deviceId} logcat -d -v time ${options.filter || '*:W'}`
);
const logLines = logcatResult.stdout.split('\n').filter(line => line.trim());
// Parse and categorize log entries
const errors = [];
const warnings = [];
const crashes = [];
for (const line of logLines) {
const logEntry = this.parseLogcatLine(line);
if (logEntry) {
if (logEntry.level === 'E') {
errors.push(logEntry);
} else if (logEntry.level === 'W') {
warnings.push(logEntry);
}
// Detect crashes
if (line.includes('FATAL EXCEPTION') || line.includes('AndroidRuntime')) {
crashes.push(logEntry);
}
}
}
// Generate analysis summary
const analysis = {
deviceId,
timestamp: Date.now(),
totalLines: logLines.length,
errors: errors.length,
warnings: warnings.length,
crashes: crashes.length,
entries: {
errors: errors.slice(0, 10), // Limit to recent entries
warnings: warnings.slice(0, 10),
crashes
},
patterns: this.identifyLogPatterns(logLines),
recommendations: this.generateLogRecommendations(errors, warnings, crashes)
};
console.log(`✅ Logcat analysis completed: ${errors.length} errors, ${warnings.length} warnings`);
return {
success: true,
analysis
};
} catch (error) {
console.error(`❌ Failed to analyze logcat:`, error.message);
return {
success: false,
error: error.message
};
}
}
/**
* Parse individual logcat line
*/
parseLogcatLine(line) {
// Android logcat format: MM-DD HH:MM:SS.mmm PID TID LEVEL TAG: MESSAGE
const logcatRegex = /^(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(\d+)\s+(\d+)\s+([VDIWEF])\s+([^:]+):\s*(.*)$/;
const match = line.match(logcatRegex);
if (match) {
return {
timestamp: match[1],
pid: parseInt(match[2]),
tid: parseInt(match[3]),
level: match[4],
tag: match[5].trim(),
message: match[6].trim(),
raw: line
};
}
return null;
}
/**
* Identify common patterns in logs
*/
identifyLogPatterns(logLines) {
const patterns = {
networkErrors: logLines.filter(line =>
line.includes('NetworkException') ||
line.includes('ConnectException') ||
line.includes('SocketTimeoutException')
).length,
memoryIssues: logLines.filter(line =>
line.includes('OutOfMemoryError') ||
line.includes('GC_') ||
line.includes('lowmemorykiller')
).length,
uiErrors: logLines.filter(line =>
line.includes('ViewRootImpl') ||
line.includes('WindowManager') ||
line.includes('IllegalStateException')
).length,
permissionErrors: logLines.filter(line =>
line.includes('SecurityException') ||
line.includes('Permission denied')
).length
};
return patterns;
}
/**
* Generate recommendations based on log analysis
*/
generateLogRecommendations(errors, warnings, crashes) {
const recommendations = [];
if (crashes.length > 0) {
recommendations.push('Critical: Application crashes detected. Review stack traces immediately.');
}
if (errors.length > 10) {
recommendations.push('High error count detected. Consider implementing better error handling.');
}
if (warnings.length > 20) {
recommendations.push('Many warnings found. Address warnings to prevent future errors.');
}
// Add specific recommendations based on error types
const errorMessages = errors.map(e => e.message).join(' ');
if (errorMessages.includes('NetworkException')) {
recommendations.push('Network connectivity issues detected. Implement retry logic and offline handling.');
}
if (errorMessages.includes('OutOfMemoryError')) {
recommendations.push('Memory issues detected. Optimize memory usage and implement proper cleanup.');
}
return recommendations;
}
/**
* Get enhanced debugging session status
*/
getDebuggingStatus() {
return {
connectedDevices: Array.from(this.connectedDevices.values()),
activeLogcatSessions: Array.from(this.logcatProcesses.keys()),
activeSessions: this.activeDebuggingSessions.size,
screenshotsCached: this.screenshotCache.size,
multiDevicePool: Array.from(this.multiDevicePool),
batteryAnalyzers: Array.from(this.batteryAnalyzers.keys()),
performanceMonitors: Array.from(this.performanceMonitors.keys()),
supportedLanguages: this.supportedLanguages,
debuggingModes: this.debuggingModes,
performanceMetrics: this.performanceMetrics,
capabilities: {
multiDevice: true,
batteryAnalysis: true,
performanceMonitoring: true,
wirelessDebugging: true,
cloudDebugging: false, // Future enhancement
crossPlatform: false // Future enhancement
}
};
}
/**
* Multi-Device Debugging: Connect to multiple devices simultaneously
*/
async connectMultipleDevices(deviceConfigs) {
console.log(`📱 Connecting to ${deviceConfigs.length} devices simultaneously...`);
const connectionResults = [];
const connectionPromises = deviceConfigs.map(async (config) => {
try {
const result = await this.connectWirelessDevice(config.ip, config.port);
if (result.success) {
this.multiDevicePool.add(result.deviceId);
}
return { ...result, config };
} catch (error) {
return {
success: false,
error: error.message,
config
};
}
});
const results = await Promise.allSettled(connectionPromises);
for (const result of results) {
if (result.status === 'fulfilled') {
connectionResults.push(result.value);
} else {
connectionResults.push({
success: false,
error: result.reason.message,
config: null
});
}
}
const successCount = connectionResults.filter(r => r.success).length;
console.log(`✅ Connected to ${successCount}/${deviceConfigs.length} devices`);
return {
success: successCount > 0,
totalDevices: deviceConfigs.length,
connectedDevices: successCount,
results: connectionResults,
multiDevicePool: Array.from(this.multiDevicePool)
};
}
/**
* Multi-Device Debugging: Take screenshots from all connected devices
*/
async takeMultiDeviceScreenshots(options = {}) {
console.log(`📸 Taking screenshots from ${this.multiDevicePool.size} devices...`);
const screenshotPromises = Array.from(this.multiDevicePool).map(async (deviceId) => {
try {
const result = await this.takeScreenshot(deviceId, options);
return { deviceId, ...result };
} catch (error) {
return {
deviceId,
success: false,
error: error.message
};
}
});
const screenshots = await Promise.allSettled(screenshotPromises);
const successfulScreenshots = screenshots
.filter(s => s.status === 'fulfilled' && s.value.success)
.map(s => s.value);
console.log(`✅ Captured ${successfulScreenshots.length} screenshots from multi-device pool`);
return {
success: successfulScreenshots.length > 0,
totalDevices: this.multiDevicePool.size,
successfulScreenshots: successfulScreenshots.length,
screenshots: successfulScreenshots
};
}
/**
* Battery Usage Analysis: Monitor battery consumption
*/
async startBatteryAnalysis(deviceId, duration = 300000) { // 5 minutes default
try {
console.log(`🔋 Starting battery analysis for device ${deviceId}...`);
const analysisId = `battery_${deviceId}_${Date.now()}`;
const batteryFile = path.join(this.batteryDir, `${analysisId}.json`);
// Get initial battery stats
const initialStats = await this.getBatteryStats(deviceId);
const batteryAnalyzer = {
id: analysisId,
deviceId,
startTime: Date.now(),
duration,
initialStats,
samples: [],
isActive: true
};
this.batteryAnalyzers.set(analysisId, batteryAnalyzer);
// Start periodic battery monitoring
const monitoringInterval = setInterval(async () => {
try {
const currentStats = await this.getBatteryStats(deviceId);
batteryAnalyzer.samples.push({
timestamp: Date.now(),
stats: currentStats
});
// Save to file periodically
await fs.writeFile(batteryFile, JSON.stringify(batteryAnalyzer, null, 2));
} catch (error) {
console.error('Battery monitoring error:', error.message);
}
}, 10000); // Sample every 10 seconds
// Stop monitoring after duration
setTimeout(async () => {
clearInterval(monitoringInterval);
batteryAnalyzer.isActive = false;
const finalAnalysis = await this.generateBatteryAnalysis(batteryAnalyzer);
batteryAnalyzer.analysis = finalAnalysis;
await fs.writeFile(batteryFile, JSON.stringify(batteryAnalyzer, null, 2));
console.log(`🔋 Battery analysis completed for ${deviceId}`);
}, duration);
return {
success: true,
analysisId,
deviceId,
duration,
message: 'Battery analysis started'
};
} catch (error) {
console.error(`❌ Failed to start battery analysis:`, error.message);
return {
success: false,
error: error.message
};
}
}
/**
* Get current battery statistics
*/
async getBatteryStats(deviceId) {
try {
const [level, temp, voltage, current] = await Promise.all([
execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep level`),
execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep temperature`),
execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep voltage`),
execAsync(`${this.adbPath} -s ${deviceId} shell dumpsys battery | grep current`)
]);
return {
level: parseInt(level.stdout.match(/\d+/)?.[0] || '0'),
temperature: parseInt(temp.stdout.match(/\d+/)?.[0] || '0') / 10, // Convert to Celsius
voltage: parseInt(voltage.stdout.match(/\d+/)?.[0] || '0'),
current: parseInt(current.stdout.match(/-?\d+/)?.[0] || '0'),
timestamp: Date.now()
};
} catch (error) {
console.error('Failed to get battery stats:', error.message);
return {
level: 0,
temperature: 0,
voltage: 0,
current: 0,
timestamp: Date.now()
};
}
}
/**
* Disconnect from device
*/
async disconnectDevice(deviceId) {
try {
// Stop logcat monitoring if active
await this.stopLogcatMonitoring(deviceId);
// Remove from multi-device pool
this.multiDevicePool.delete(deviceId);
// Stop battery analysis if active
for (const [analysisId, analyzer] of this.batteryAnalyzers) {
if (analyzer.deviceId === deviceId) {
analyzer.isActive = false;
this.batteryAnalyzers.delete(analysisId);
}
}
// Disconnect device
await execAsync(`${this.adbPath} disconnect ${deviceId}`);
// Remove from connected devices
this.connectedDevices.delete(deviceId);
console.log(`✅ Disconnected from device ${deviceId}`);
return {
success: true,
message: `Disconnected from ${deviceId}`
};
} catch (error) {
console.error(`❌ Failed to disconnect from ${deviceId}:`, error.message);
return {
success: false,
error: error.message
};
}
}
}