33// found in the LICENSE file.
44
55import 'dart:async' ;
6+ import 'dart:convert' ;
67
78import '../base/common.dart' ;
89import '../base/context.dart' ;
9- import '../base/file_system.dart' ;
1010import '../base/io.dart' ;
11- import '../base/os.dart' ;
1211import '../base/platform.dart' ;
1312import '../base/process.dart' ;
1413import '../base/process_manager.dart' ;
@@ -17,10 +16,20 @@ import '../base/version.dart';
1716import '../doctor.dart' ;
1817import '../globals.dart' ;
1918import 'android_sdk.dart' ;
20- import 'android_studio.dart' as android_studio;
2119
2220AndroidWorkflow get androidWorkflow => context.putIfAbsent (AndroidWorkflow , () => new AndroidWorkflow ());
2321
22+ enum LicensesAccepted {
23+ none,
24+ some,
25+ all,
26+ unknown,
27+ }
28+
29+ final RegExp licenseCounts = new RegExp (r'(\d+) of (\d+) SDK package licenses? not accepted.' );
30+ final RegExp licenseNotAccepted = new RegExp (r'licenses? not accepted' , caseSensitive: false );
31+ final RegExp licenseAccepted = new RegExp (r'All SDK package licenses accepted.' );
32+
2433class AndroidWorkflow extends DoctorValidator implements Workflow {
2534 AndroidWorkflow () : super ('Android toolchain - develop for Android devices' );
2635
@@ -33,41 +42,8 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
3342 @override
3443 bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed ().isEmpty;
3544
36- static const String _kJavaHomeEnvironmentVariable = 'JAVA_HOME' ;
37- static const String _kJavaExecutable = 'java' ;
3845 static const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/' ;
3946
40- /// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
41- static String _findJavaBinary () {
42-
43- if (android_studio.javaPath != null )
44- return fs.path.join (android_studio.javaPath, 'bin' , 'java' );
45-
46- final String javaHomeEnv = platform.environment[_kJavaHomeEnvironmentVariable];
47- if (javaHomeEnv != null ) {
48- // Trust JAVA_HOME.
49- return fs.path.join (javaHomeEnv, 'bin' , 'java' );
50- }
51-
52- // MacOS specific logic to avoid popping up a dialog window.
53- // See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
54- if (platform.isMacOS) {
55- try {
56- final String javaHomeOutput = runCheckedSync (< String > ['/usr/libexec/java_home' ], hideStdout: true );
57- if (javaHomeOutput != null ) {
58- final List <String > javaHomeOutputSplit = javaHomeOutput.split ('\n ' );
59- if ((javaHomeOutputSplit != null ) && (javaHomeOutputSplit.isNotEmpty)) {
60- final String javaHome = javaHomeOutputSplit[0 ].trim ();
61- return fs.path.join (javaHome, 'bin' , 'java' );
62- }
63- }
64- } catch (_) { /* ignore */ }
65- }
66-
67- // Fallback to PATH based lookup.
68- return os.which (_kJavaExecutable)? .path;
69- }
70-
7147 /// Returns false if we cannot determine the Java version or if the version
7248 /// is not compatible.
7349 bool _checkJavaVersion (String javaBinary, List <ValidationMessage > messages) {
@@ -154,7 +130,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
154130 }
155131
156132 // Now check for the JDK.
157- final String javaBinary = _findJavaBinary ();
133+ final String javaBinary = AndroidSdk . findJavaBinary ();
158134 if (javaBinary == null ) {
159135 messages.add (new ValidationMessage .error (
160136 'No Java Development Kit (JDK) found; You must have the environment '
@@ -169,25 +145,66 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
169145 return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
170146 }
171147
148+ // Check for licenses.
149+ switch (await licensesAccepted) {
150+ case LicensesAccepted .all:
151+ messages.add (new ValidationMessage ('All Android licenses accepted.' ));
152+ break ;
153+ case LicensesAccepted .some:
154+ messages.add (new ValidationMessage .hint ('Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses' ));
155+ return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
156+ case LicensesAccepted .none:
157+ messages.add (new ValidationMessage .error ('Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses' ));
158+ return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
159+ case LicensesAccepted .unknown:
160+ messages.add (new ValidationMessage .error ('Android license status unknown.' ));
161+ return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
162+ }
163+
172164 // Success.
173165 return new ValidationResult (ValidationType .installed, messages, statusInfo: sdkVersionText);
174166 }
175167
168+ Future <LicensesAccepted > get licensesAccepted async {
169+ LicensesAccepted status = LicensesAccepted .unknown;
170+
171+ void _onLine (String line) {
172+ if (licenseAccepted.hasMatch (line)) {
173+ status = LicensesAccepted .all;
174+ } else if (licenseCounts.hasMatch (line)) {
175+ final Match match = licenseCounts.firstMatch (line);
176+ if (match.group (1 ) != match.group (2 )) {
177+ status = LicensesAccepted .some;
178+ } else {
179+ status = LicensesAccepted .none;
180+ }
181+ } else if (licenseNotAccepted.hasMatch (line)) {
182+ // In case the format changes, a more general match will keep doctor
183+ // mostly working.
184+ status = LicensesAccepted .none;
185+ }
186+ }
187+
188+ final Process process = await runCommand (< String > [androidSdk.sdkManagerPath, '--licenses' ], environment: androidSdk.sdkManagerEnv);
189+ process.stdin.write ('n\n ' );
190+ final Future <void > output = process.stdout.transform (const Utf8Decoder (allowMalformed: true )).transform (const LineSplitter ()).listen (_onLine).asFuture <void >(null );
191+ final Future <void > errors = process.stderr.transform (const Utf8Decoder (allowMalformed: true )).transform (const LineSplitter ()).listen (_onLine).asFuture <void >(null );
192+ try {
193+ await Future .wait <void >(< Future <void >> [output, errors]).timeout (const Duration (seconds: 30 ));
194+ } catch (TimeoutException ) {
195+ printTrace ('Intentionally killing ${androidSdk .sdkManagerPath }' );
196+ processManager.killPid (process.pid);
197+ }
198+ return status;
199+ }
200+
176201 /// Run the Android SDK manager tool in order to accept SDK licenses.
177202 static Future <bool > runLicenseManager () async {
178203 if (androidSdk == null ) {
179204 printStatus ('Unable to locate Android SDK.' );
180205 return false ;
181206 }
182207
183- // If we can locate Java, then add it to the path used to run the Android SDK manager.
184- final Map <String , String > sdkManagerEnv = < String , String > {};
185- final String javaBinary = _findJavaBinary ();
186- if (javaBinary != null ) {
187- sdkManagerEnv['PATH' ] =
188- fs.path.dirname (javaBinary) + os.pathVarSeparator + platform.environment['PATH' ];
189- }
190-
191208 if (! processManager.canRun (androidSdk.sdkManagerPath))
192209 throwToolExit (
193210 'Android sdkmanager tool not found.\n '
@@ -205,7 +222,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
205222
206223 final Process process = await runCommand (
207224 < String > [androidSdk.sdkManagerPath, '--licenses' ],
208- environment: sdkManagerEnv,
225+ environment: androidSdk. sdkManagerEnv,
209226 );
210227
211228 waitGroup <Null >(< Future <Null >> [
0 commit comments