allow any android sdk version
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
new file mode 100644
index 0000000..0271f3a
--- /dev/null
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -0,0 +1,214 @@
+// Copyright 2016 The Chromium 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 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:pub_semver/pub_semver.dart';
+
+import '../base/globals.dart';
+import '../base/os.dart';
+
+// Android SDK layout:
+//
+// $ANDROID_HOME/platform-tools/adb
+// $ANDROID_HOME/build-tools/19.1.0/aapt, dx, zipalign
+// $ANDROID_HOME/build-tools/22.0.1/aapt
+// $ANDROID_HOME/build-tools/23.0.2/aapt
+// $ANDROID_HOME/platforms/android-22/android.jar
+// $ANDROID_HOME/platforms/android-23/android.jar
+
+// TODO(devoncarew): We need a way to locate the Android SDK w/o using an environment variable.
+// Perhaps something like `flutter config --android-home=foo/bar`.
+
+/// Locate ADB. Prefer to use one from an Android SDK, if we can locate that.
+String getAdbPath() {
+ AndroidSdk sdk = AndroidSdk.locateAndroidSdk();
+
+ if (sdk?.latestVersion == null) {
+ return os.which('adb')?.path;
+ } else {
+ return sdk.adbPath;
+ }
+}
+
+class AndroidSdk {
+ AndroidSdk(this.directory) {
+ _init();
+ }
+
+ final String directory;
+
+ List<AndroidSdkVersion> _sdkVersions;
+ AndroidSdkVersion _latestVersion;
+
+ static AndroidSdk locateAndroidSdk() {
+ // TODO: Use explicit configuration information from a metadata file?
+
+ if (Platform.environment.containsKey('ANDROID_HOME')) {
+ String homeDir = Platform.environment['ANDROID_HOME'];
+ if (validSdkDirectory(homeDir))
+ return new AndroidSdk(homeDir);
+ if (validSdkDirectory(path.join(homeDir, 'sdk')))
+ return new AndroidSdk(path.join(homeDir, 'sdk'));
+ }
+
+ File aaptBin = os.which('aapt'); // in build-tools/$version/aapt
+ if (aaptBin != null) {
+ String dir = aaptBin.parent.parent.parent.path;
+ if (validSdkDirectory(dir))
+ return new AndroidSdk(dir);
+ }
+
+ File adbBin = os.which('adb'); // in platform-tools/adb
+ if (adbBin != null) {
+ String dir = adbBin.parent.parent.path;
+ if (validSdkDirectory(dir))
+ return new AndroidSdk(dir);
+ }
+
+ // No dice.
+ printTrace('Unable to locate an Android SDK.');
+ return null;
+ }
+
+ static bool validSdkDirectory(String dir) {
+ return FileSystemEntity.isDirectorySync(path.join(dir, 'platform-tools'));
+ }
+
+ List<AndroidSdkVersion> get sdkVersions => _sdkVersions;
+
+ AndroidSdkVersion get latestVersion => _latestVersion;
+
+ String get adbPath => getPlatformToolsPath('adb');
+
+ bool validateSdkWellFormed({ bool complain: false }) {
+ if (!FileSystemEntity.isFileSync(adbPath)) {
+ if (complain)
+ printError('Android SDK file not found: $adbPath.');
+ return false;
+ }
+
+ if (sdkVersions.isEmpty) {
+ if (complain)
+ printError('Android SDK does not have the proper build-tools.');
+ return false;
+ }
+
+ return latestVersion.validateSdkWellFormed(complain: complain);
+ }
+
+ String getPlatformToolsPath(String binaryName) {
+ return path.join(directory, 'platform-tools', binaryName);
+ }
+
+ void _init() {
+ List<String> platforms = <String>[]; // android-22, ...
+
+ Directory platformsDir = new Directory(path.join(directory, 'platforms'));
+ if (platformsDir.existsSync()) {
+ platforms = platformsDir
+ .listSync()
+ .map((FileSystemEntity entity) => path.basename(entity.path))
+ .where((String name) => name.startsWith('android-'))
+ .toList();
+ }
+
+ List<Version> buildToolsVersions = <Version>[]; // 19.1.0, 22.0.1, ...
+
+ Directory buildToolsDir = new Directory(path.join(directory, 'build-tools'));
+ if (buildToolsDir.existsSync()) {
+ buildToolsVersions = buildToolsDir
+ .listSync()
+ .map((FileSystemEntity entity) {
+ try {
+ return new Version.parse(path.basename(entity.path));
+ } catch (error) {
+ return null;
+ }
+ })
+ .where((Version version) => version != null)
+ .toList();
+ }
+
+ // Here we match up platforms with cooresponding build-tools. If we don't
+ // have a match, we don't return anything for that platform version. So if
+ // the user only have 'android-22' and 'build-tools/19.0.0', we don't find
+ // an Android sdk.
+ _sdkVersions = platforms.map((String platform) {
+ int sdkVersion;
+
+ try {
+ sdkVersion = int.parse(platform.substring('android-'.length));
+ } catch (error) {
+ return null;
+ }
+
+ Version buildToolsVersion = Version.primary(buildToolsVersions.where((Version version) {
+ return version.major == sdkVersion;
+ }).toList());
+
+ if (buildToolsVersion == null)
+ return null;
+
+ return new AndroidSdkVersion(this, platform, buildToolsVersion.toString());
+ }).where((AndroidSdkVersion version) => version != null).toList();
+
+ _sdkVersions.sort();
+
+ _latestVersion = _sdkVersions.isEmpty ? null : _sdkVersions.last;
+ }
+
+ String toString() => 'AndroidSdk: $directory';
+}
+
+class AndroidSdkVersion implements Comparable<AndroidSdkVersion> {
+ AndroidSdkVersion(this.sdk, this.androidVersion, this.buildToolsVersion);
+
+ final AndroidSdk sdk;
+ final String androidVersion;
+ final String buildToolsVersion;
+
+ int get sdkLevel => int.parse(androidVersion.substring('android-'.length));
+
+ String get androidJarPath => getPlatformsPath('android.jar');
+
+ String get aaptPath => getBuildToolsPath('aapt');
+
+ String get dxPath => getBuildToolsPath('dx');
+
+ String get zipalignPath => getBuildToolsPath('zipalign');
+
+ bool validateSdkWellFormed({ bool complain: false }) {
+ return
+ _exists(androidJarPath, complain: complain) &&
+ _exists(aaptPath, complain: complain) &&
+ _exists(dxPath, complain: complain) &&
+ _exists(zipalignPath, complain: complain);
+ }
+
+ String getPlatformsPath(String itemName) {
+ return path.join(sdk.directory, 'platforms', androidVersion, itemName);
+ }
+
+ String getBuildToolsPath(String binaryName) {
+ return path.join(sdk.directory, 'build-tools', buildToolsVersion, binaryName);
+ }
+
+ int compareTo(AndroidSdkVersion other) {
+ return sdkLevel - other.sdkLevel;
+ }
+
+ String toString() => '[${sdk.directory}, SDK version $sdkLevel, build-tools $buildToolsVersion]';
+
+ bool _exists(String path, { bool complain: false }) {
+ if (!FileSystemEntity.isFileSync(path)) {
+ if (complain)
+ printError('Android SDK file not found: $path.');
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/packages/flutter_tools/lib/src/android/device_android.dart b/packages/flutter_tools/lib/src/android/device_android.dart
index c55e0d4..0f7c4d0 100644
--- a/packages/flutter_tools/lib/src/android/device_android.dart
+++ b/packages/flutter_tools/lib/src/android/device_android.dart
@@ -50,18 +50,6 @@
}) : super(id) {
if (connected != null)
_connected = connected;
-
- _adbPath = getAdbPath();
- _hasAdb = _checkForAdb();
-
- // Checking for [minApiName] only needs to be done if we are starting an
- // app, but it has an important side effect, which is to discard any
- // progress messages if the adb server is restarted.
- _hasValidAndroid = _checkForSupportedAndroidVersion();
-
- if (!_hasAdb || !_hasValidAndroid) {
- printError('Unable to run on Android.');
- }
}
final String productID;
@@ -69,32 +57,9 @@
final String deviceCodeName;
bool _connected;
- String _adbPath;
- String get adbPath => _adbPath;
- bool _hasAdb = false;
- bool _hasValidAndroid = false;
-
- static String getAndroidSdkPath() {
- if (Platform.environment.containsKey('ANDROID_HOME')) {
- String androidHomeDir = Platform.environment['ANDROID_HOME'];
- if (FileSystemEntity.isDirectorySync(
- path.join(androidHomeDir, 'platform-tools'))) {
- return androidHomeDir;
- } else if (FileSystemEntity.isDirectorySync(
- path.join(androidHomeDir, 'sdk', 'platform-tools'))) {
- return path.join(androidHomeDir, 'sdk');
- } else {
- printError('Android SDK not found at $androidHomeDir');
- return null;
- }
- } else {
- printError('Android SDK not found. The ANDROID_HOME variable must be set.');
- return null;
- }
- }
List<String> adbCommandForDevice(List<String> args) {
- return <String>[adbPath, '-s', id]..addAll(args);
+ return <String>[androidSdk.adbPath, '-s', id]..addAll(args);
}
bool _isValidAdbVersion(String adbVersion) {
@@ -121,24 +86,19 @@
return true;
}
- bool _checkForAdb() {
- try {
- String adbVersion = runCheckedSync(<String>[adbPath, 'version']);
- if (_isValidAdbVersion(adbVersion)) {
- return true;
- }
+ bool _checkForSupportedAdbVersion() {
+ if (androidSdk == null)
+ return false;
- String locatedAdbPath = runCheckedSync(<String>['which', 'adb']);
- printError('"$locatedAdbPath" is too old. '
- 'Please install version 1.0.32 or later.\n'
- 'Try setting ANDROID_HOME to the path to your Android SDK install. '
- 'Android builds are unavailable.');
- } catch (e) {
- printError('"adb" not found in \$PATH. '
- 'Please install the Android SDK or set ANDROID_HOME '
- 'to the path of your Android SDK install.');
- printTrace('$e');
+ try {
+ String adbVersion = runCheckedSync(<String>[androidSdk.adbPath, 'version']);
+ if (_isValidAdbVersion(adbVersion))
+ return true;
+ printError('The ADB at "${androidSdk.adbPath}" is too old; please install version 1.0.32 or later.');
+ } catch (error, trace) {
+ printError('Error running ADB: $error', trace);
}
+
return false;
}
@@ -150,34 +110,29 @@
// * daemon started successfully *
runCheckedSync(adbCommandForDevice(<String>['start-server']));
- String ready = runSync(adbCommandForDevice(<String>['shell', 'echo', 'ready']));
- if (ready.trim() != 'ready') {
- printTrace('Android device not found.');
- return false;
- }
-
// Sample output: '22'
String sdkVersion = runCheckedSync(
adbCommandForDevice(<String>['shell', 'getprop', 'ro.build.version.sdk'])
).trimRight();
- int sdkVersionParsed =
- int.parse(sdkVersion, onError: (String source) => null);
+ int sdkVersionParsed = int.parse(sdkVersion, onError: (String source) => null);
if (sdkVersionParsed == null) {
printError('Unexpected response from getprop: "$sdkVersion"');
return false;
}
+
if (sdkVersionParsed < minApiLevel) {
printError(
'The Android version ($sdkVersion) on the target device is too old. Please '
'use a $minVersionName (version $minApiLevel / $minVersionText) device or later.');
return false;
}
+
return true;
} catch (e) {
printError('Unexpected failure from adb: $e');
+ return false;
}
- return false;
}
String _getDeviceSha1Path(ApplicationPackage app) {
@@ -220,11 +175,15 @@
printTrace('Android device not connected. Not installing.');
return false;
}
+
if (!FileSystemEntity.isFileSync(app.localPath)) {
printError('"${app.localPath}" does not exist.');
return false;
}
+ if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
+ return false;
+
printStatus('Installing ${app.name} on device.');
runCheckedSync(adbCommandForDevice(<String>['install', '-r', app.localPath]));
runCheckedSync(adbCommandForDevice(<String>['shell', 'echo', '-n', _getSourceSha1(app), '>', _getDeviceSha1Path(app)]));
@@ -306,6 +265,9 @@
int debugPort: observatoryDefaultPort,
Map<String, dynamic> platformArgs
}) async {
+ if (!_checkForSupportedAdbVersion() || !_checkForSupportedAndroidVersion())
+ return false;
+
flx.DirectoryResult buildResult = await flx.buildInTempDir(
toolchain,
mainPath: mainPath
@@ -420,7 +382,7 @@
return null;
}
- bool isConnected() => _connected ?? _hasValidAndroid;
+ bool isConnected() => _connected ?? androidSdk != null;
void setConnected(bool value) {
_connected = value;
@@ -447,18 +409,12 @@
}
}
-/// The [mockAndroid] argument is only to facilitate testing with mocks, so that
-/// we don't have to rely on the test setup having adb available to it.
-List<AndroidDevice> getAdbDevices([AndroidDevice mockAndroid]) {
- List<AndroidDevice> devices = [];
- String adbPath = (mockAndroid != null) ? mockAndroid.adbPath : getAdbPath();
+List<AndroidDevice> getAdbDevices() {
+ if (androidSdk == null)
+ return <AndroidDevice>[];
- try {
- runCheckedSync(<String>[adbPath, 'version']);
- } catch (e) {
- printError('Unable to find adb. Is "adb" in your path?');
- return devices;
- }
+ String adbPath = androidSdk.adbPath;
+ List<AndroidDevice> devices = [];
List<String> output = runSync(<String>[adbPath, 'devices', '-l']).trim().split('\n');
@@ -525,25 +481,6 @@
return devices;
}
-String getAdbPath() {
- if (Platform.environment.containsKey('ANDROID_HOME')) {
- String androidHomeDir = Platform.environment['ANDROID_HOME'];
- String adbPath1 = path.join(androidHomeDir, 'sdk', 'platform-tools', 'adb');
- String adbPath2 = path.join(androidHomeDir, 'platform-tools', 'adb');
- if (FileSystemEntity.isFileSync(adbPath1)) {
- return adbPath1;
- } else if (FileSystemEntity.isFileSync(adbPath2)) {
- return adbPath2;
- } else {
- printTrace('"adb" not found at\n "$adbPath1" or\n "$adbPath2"\n' +
- 'using default path "$_defaultAdbPath"');
- return _defaultAdbPath;
- }
- } else {
- return _defaultAdbPath;
- }
-}
-
/// A log reader that logs from `adb logcat`. This will have the same output as
/// another copy of [_AdbLogReader], and the two instances will be equivalent.
class _AdbLogReader extends DeviceLogReader {