// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:meta/meta.dart'; import 'package:process/process.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; import '../base/platform.dart'; import '../base/process.dart'; import '../device.dart'; import '../globals.dart' as globals; import 'adb.dart'; import 'android_device.dart'; import 'android_sdk.dart'; import 'android_workflow.dart' hide androidWorkflow; import 'android_workflow.dart' as workflow show androidWorkflow; /// Device discovery for Android physical devices and emulators.s class AndroidDevices extends PollingDeviceDiscovery { // TODO(jonahwilliams): make these required after google3 is updated. AndroidDevices({ AndroidWorkflow androidWorkflow, ProcessManager processManager, Logger logger, AndroidSdk androidSdk, }) : _androidWorkflow = androidWorkflow ?? workflow.androidWorkflow, _androidSdk = androidSdk ?? globals.androidSdk, _processUtils = ProcessUtils( logger: logger ?? globals.logger, processManager: processManager ?? globals.processManager, ), super('Android devices'); final AndroidWorkflow _androidWorkflow; final ProcessUtils _processUtils; final AndroidSdk _androidSdk; @override bool get supportsPlatform => true; @override bool get canListAnything => _androidWorkflow.canListDevices; @override Future<List<Device>> pollingGetDevices({ Duration timeout }) async { final String adbPath = getAdbPath(_androidSdk); if (adbPath == null) { return <AndroidDevice>[]; } String text; try { text = (await _processUtils.run( <String>[adbPath, 'devices', '-l'], throwOnError: true, )).stdout.trim(); } on ArgumentError catch (exception) { throwToolExit('Unable to find "adb", check your Android SDK installation and ' '$kAndroidSdkRoot environment variable: ${exception.message}'); } on ProcessException catch (exception) { throwToolExit('Unable to run "adb", check your Android SDK installation and ' '$kAndroidSdkRoot environment variable: ${exception.executable}'); } final List<AndroidDevice> devices = <AndroidDevice>[]; parseADBDeviceOutput( text, devices: devices, timeoutConfiguration: timeoutConfiguration, ); return devices; } @override Future<List<String>> getDiagnostics() async { final String adbPath = getAdbPath(_androidSdk); if (adbPath == null) { return <String>[]; } final RunResult result = await _processUtils.run(<String>[adbPath, 'devices', '-l']); if (result.exitCode != 0) { return <String>[]; } else { final String text = result.stdout; final List<String> diagnostics = <String>[]; parseADBDeviceOutput( text, diagnostics: diagnostics, timeoutConfiguration: timeoutConfiguration, ); return diagnostics; } } // 015d172c98400a03 device usb:340787200X product:nakasi model:Nexus_7 device:grouper static final RegExp _kDeviceRegex = RegExp(r'^(\S+)\s+(\S+)(.*)'); /// Parse the given `adb devices` output in [text], and fill out the given list /// of devices and possible device issue diagnostics. Either argument can be null, /// in which case information for that parameter won't be populated. @visibleForTesting static void parseADBDeviceOutput( String text, { List<AndroidDevice> devices, List<String> diagnostics, AndroidSdk androidSdk, FileSystem fileSystem, Logger logger, Platform platform, ProcessManager processManager, @required TimeoutConfiguration timeoutConfiguration, }) { // Check for error messages from adb if (!text.contains('List of devices')) { diagnostics?.add(text); return; } for (final String line in text.trim().split('\n')) { // Skip lines like: * daemon started successfully * if (line.startsWith('* daemon ')) { continue; } // Skip lines about adb server and client version not matching if (line.startsWith(RegExp(r'adb server (version|is out of date)'))) { diagnostics?.add(line); continue; } if (line.startsWith('List of devices')) { continue; } if (_kDeviceRegex.hasMatch(line)) { final Match match = _kDeviceRegex.firstMatch(line); final String deviceID = match[1]; final String deviceState = match[2]; String rest = match[3]; final Map<String, String> info = <String, String>{}; if (rest != null && rest.isNotEmpty) { rest = rest.trim(); for (final String data in rest.split(' ')) { if (data.contains(':')) { final List<String> fields = data.split(':'); info[fields[0]] = fields[1]; } } } if (info['model'] != null) { info['model'] = cleanAdbDeviceName(info['model']); } if (deviceState == 'unauthorized') { diagnostics?.add( 'Device $deviceID is not authorized.\n' 'You might need to check your device for an authorization dialog.' ); } else if (deviceState == 'offline') { diagnostics?.add('Device $deviceID is offline.'); } else { devices?.add(AndroidDevice( deviceID, productID: info['product'], modelID: info['model'] ?? deviceID, deviceCodeName: info['device'], androidSdk: androidSdk ?? globals.androidSdk, fileSystem: fileSystem ?? globals.fs, logger: logger ?? globals.logger, platform: platform ?? globals.platform, processManager: processManager ?? globals.processManager, timeoutConfiguration: timeoutConfiguration, )); } } else { diagnostics?.add( 'Unexpected failure parsing device information from adb output:\n' '$line\n' '${globals.userMessages.flutterToolBugInstructions}'); } } } }