| // 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: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 '../base/user_messages.dart'; |
| import '../device.dart'; |
| import 'adb.dart'; |
| import 'android_device.dart'; |
| import 'android_sdk.dart'; |
| import 'android_workflow.dart'; |
| |
| /// Device discovery for Android physical devices and emulators. |
| /// |
| /// This class primarily delegates to the `adb` command line tool provided by |
| /// the Android SDK to discover instances of connected android devices. |
| /// |
| /// See also: |
| /// * [AndroidDevice], the type of discovered device. |
| class AndroidDevices extends PollingDeviceDiscovery { |
| AndroidDevices({ |
| required AndroidWorkflow androidWorkflow, |
| required ProcessManager processManager, |
| required Logger logger, |
| AndroidSdk? androidSdk, |
| required FileSystem fileSystem, |
| required Platform platform, |
| required UserMessages userMessages, |
| }) : _androidWorkflow = androidWorkflow, |
| _androidSdk = androidSdk, |
| _processUtils = ProcessUtils( |
| logger: logger, |
| processManager: processManager, |
| ), |
| _processManager = processManager, |
| _logger = logger, |
| _fileSystem = fileSystem, |
| _platform = platform, |
| _userMessages = userMessages, |
| super('Android devices'); |
| |
| final AndroidWorkflow _androidWorkflow; |
| final ProcessUtils _processUtils; |
| final AndroidSdk? _androidSdk; |
| final ProcessManager _processManager; |
| final Logger _logger; |
| final FileSystem _fileSystem; |
| final Platform _platform; |
| final UserMessages _userMessages; |
| |
| @override |
| bool get supportsPlatform => _androidWorkflow.appliesToHostPlatform; |
| |
| @override |
| bool get canListAnything => _androidWorkflow.canListDevices; |
| |
| @override |
| Future<List<Device>> pollingGetDevices({ Duration? timeout }) async { |
| if (_doesNotHaveAdb()) { |
| return <AndroidDevice>[]; |
| } |
| String text; |
| try { |
| text = (await _processUtils.run(<String>[_androidSdk!.adbPath!, 'devices', '-l'], |
| throwOnError: true, |
| )).stdout.trim(); |
| } 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, |
| ); |
| return devices; |
| } |
| |
| @override |
| Future<List<String>> getDiagnostics() async { |
| if (_doesNotHaveAdb()) { |
| return <String>[]; |
| } |
| |
| final RunResult result = await _processUtils.run(<String>[_androidSdk!.adbPath!, 'devices', '-l']); |
| if (result.exitCode != 0) { |
| return <String>[]; |
| } |
| final List<String> diagnostics = <String>[]; |
| _parseADBDeviceOutput( |
| result.stdout, |
| diagnostics: diagnostics, |
| ); |
| return diagnostics; |
| } |
| |
| bool _doesNotHaveAdb() { |
| return _androidSdk == null || |
| _androidSdk.adbPath == null || |
| !_processManager.canRun(_androidSdk.adbPath); |
| } |
| |
| // 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. |
| void _parseADBDeviceOutput( |
| String text, { |
| List<AndroidDevice>? devices, |
| List<String>? diagnostics, |
| }) { |
| // 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]; |
| } |
| } |
| } |
| |
| final String? model = info['model']; |
| if (model != null) { |
| info['model'] = cleanAdbDeviceName(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!, |
| fileSystem: _fileSystem, |
| logger: _logger, |
| platform: _platform, |
| processManager: _processManager, |
| )); |
| } |
| } else { |
| diagnostics?.add( |
| 'Unexpected failure parsing device information from adb output:\n' |
| '$line\n' |
| '${_userMessages.flutterToolBugInstructions}'); |
| } |
| } |
| } |
| |
| @override |
| List<String> get wellKnownIds => const <String>[]; |
| } |