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 {
diff --git a/packages/flutter_tools/lib/src/base/context.dart b/packages/flutter_tools/lib/src/base/context.dart
index 8c1f54d..87b9da4 100644
--- a/packages/flutter_tools/lib/src/base/context.dart
+++ b/packages/flutter_tools/lib/src/base/context.dart
@@ -16,6 +16,14 @@
 class AppContext {
   Map<Type, dynamic> _instances = <Type, dynamic>{};
 
+  bool isSet(Type type) {
+    if (_instances.containsKey(type))
+      return true;
+
+    AppContext parent = _calcParent(Zone.current);
+    return parent != null ? parent.isSet(type) : false;
+  }
+
   dynamic getVariable(Type type) {
     if (_instances.containsKey(type))
       return _instances[type];
diff --git a/packages/flutter_tools/lib/src/base/globals.dart b/packages/flutter_tools/lib/src/base/globals.dart
index 5c86fb2..ebe3c12 100644
--- a/packages/flutter_tools/lib/src/base/globals.dart
+++ b/packages/flutter_tools/lib/src/base/globals.dart
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../android/android_sdk.dart';
 import '../device.dart';
 import 'context.dart';
 import 'logger.dart';
 
 DeviceManager get deviceManager => context[DeviceManager];
 Logger get logger => context[Logger];
+AndroidSdk get androidSdk => context[AndroidSdk];
 
 /// Display an error level message to the user. Commands should use this if they
 /// fail in some way.
diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart
index df10b82..444991c 100644
--- a/packages/flutter_tools/lib/src/base/os.dart
+++ b/packages/flutter_tools/lib/src/base/os.dart
@@ -18,12 +18,26 @@
 
   /// Make the given file executable. This may be a no-op on some platforms.
   ProcessResult makeExecutable(File file);
+
+  /// Return the path (with symlinks resolved) to the given executable, or `null`
+  /// if `which` was not able to locate the binary.
+  File which(String execName);
 }
 
 class _PosixUtils implements OperatingSystemUtils {
   ProcessResult makeExecutable(File file) {
     return Process.runSync('chmod', ['u+x', file.path]);
   }
+
+  /// Return the path (with symlinks resolved) to the given executable, or `null`
+  /// if `which` was not able to locate the binary.
+  File which(String execName) {
+    ProcessResult result = Process.runSync('which', <String>[execName]);
+    if (result.exitCode != 0)
+      return null;
+    String path = result.stdout.trim().split('\n').first.trim();
+    return new File(new File(path).resolveSymbolicLinksSync());
+  }
 }
 
 class _WindowsUtils implements OperatingSystemUtils {
@@ -31,6 +45,10 @@
   ProcessResult makeExecutable(File file) {
     return new ProcessResult(0, 0, null, null);
   }
+
+  File which(String execName) {
+    throw new UnimplementedError('_WindowsUtils.which');
+  }
 }
 
 Future<int> findAvailablePort() async {
diff --git a/packages/flutter_tools/lib/src/commands/apk.dart b/packages/flutter_tools/lib/src/commands/apk.dart
index 4a9899e..e6d928e 100644
--- a/packages/flutter_tools/lib/src/commands/apk.dart
+++ b/packages/flutter_tools/lib/src/commands/apk.dart
@@ -7,11 +7,12 @@
 
 import 'package:path/path.dart' as path;
 
-import '../android/device_android.dart';
+import '../android/android_sdk.dart';
 import '../application_package.dart';
 import '../artifacts.dart';
 import '../base/file_system.dart';
 import '../base/globals.dart';
+import '../base/os.dart';
 import '../base/process.dart';
 import '../build_configuration.dart';
 import '../device.dart';
@@ -34,9 +35,6 @@
 // Password for the Chromium debug keystore
 const String _kDebugKeystorePassword = "chromium";
 
-const String _kAndroidPlatformVersion = '22';
-const String _kBuildToolsVersion = '22.0.1';
-
 /// Copies files into a new directory structure.
 class _AssetBuilder {
   final Directory outDir;
@@ -59,30 +57,24 @@
 
 /// Builds an APK package using Android SDK tools.
 class _ApkBuilder {
-  final String androidSdk;
+  final AndroidSdkVersion sdk;
 
   File _androidJar;
   File _aapt;
   File _dx;
   File _zipalign;
-  String _jarsigner;
+  File _jarsigner;
 
-  _ApkBuilder(this.androidSdk) {
-    _androidJar = new File('$androidSdk/platforms/android-$_kAndroidPlatformVersion/android.jar');
-
-    String buildTools = '$androidSdk/build-tools/$_kBuildToolsVersion';
-    _aapt = new File('$buildTools/aapt');
-    _dx = new File('$buildTools/dx');
-    _zipalign = new File('$buildTools/zipalign');
-    _jarsigner = 'jarsigner';
-  }
-
-  bool checkSdkPath() {
-    return (_androidJar.existsSync() && _aapt.existsSync() && _dx.existsSync() && _zipalign.existsSync());
+  _ApkBuilder(this.sdk) {
+    _androidJar = new File(sdk.androidJarPath);
+    _aapt = new File(sdk.aaptPath);
+    _dx = new File(sdk.dxPath);
+    _zipalign = new File(sdk.zipalignPath);
+    _jarsigner = os.which('jarsigner');
   }
 
   void compileClassesDex(File classesDex, List<File> jars) {
-    List<String> packageArgs = [_dx.path,
+    List<String> packageArgs = <String>[_dx.path,
       '--dex',
       '--force-jumbo',
       '--output', classesDex.path
@@ -94,7 +86,7 @@
   }
 
   void package(File outputApk, File androidManifest, Directory assets, Directory artifacts, Directory resources) {
-    List<String> packageArgs = [_aapt.path,
+    List<String> packageArgs = <String>[_aapt.path,
       'package',
       '-M', androidManifest.path,
       '-A', assets.path,
@@ -109,7 +101,7 @@
   }
 
   void sign(File keystore, String keystorePassword, String keyAlias, String keyPassword, File outputApk) {
-    runCheckedSync([_jarsigner,
+    runCheckedSync(<String>[_jarsigner.path,
       '-keystore', keystore.path,
       '-storepass', keystorePassword,
       '-keypass', keyPassword,
@@ -119,12 +111,11 @@
   }
 
   void align(File unalignedApk, File outputApk) {
-    runCheckedSync([_zipalign.path, '-f', '4', unalignedApk.path, outputApk.path]);
+    runCheckedSync(<String>[_zipalign.path, '-f', '4', unalignedApk.path, outputApk.path]);
   }
 }
 
 class _ApkComponents {
-  Directory androidSdk;
   File manifest;
   File icuData;
   List<File> jars;
@@ -135,11 +126,12 @@
 }
 
 class ApkKeystoreInfo {
+  ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword });
+
   String keystore;
   String password;
   String keyAlias;
   String keyPassword;
-  ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword });
 }
 
 class ApkCommand extends FlutterCommand {
@@ -183,7 +175,19 @@
 
   @override
   Future<int> runInProject() async {
+    // Validate that we can find an android sdk.
+    if (androidSdk == null) {
+      printError('No Android SDK found.');
+      return 1;
+    }
+
+    if (!androidSdk.validateSdkWellFormed(complain: true)) {
+      printError('Try re-installing or updating your Android SDK.');
+      return 1;
+    }
+
     await downloadToolchain();
+
     return await buildAndroid(
       toolchain: toolchain,
       configs: buildConfigurations,
@@ -207,10 +211,8 @@
 Future<_ApkComponents> _findApkComponents(
   BuildConfiguration config, String enginePath, String manifest, String resources
 ) async {
-  String androidSdkPath;
   List<String> artifactPaths;
   if (enginePath != null) {
-    androidSdkPath = '$enginePath/third_party/android_tools/sdk';
     artifactPaths = [
       '$enginePath/third_party/icu/android/icudtl.dat',
       '${config.buildDir}/gen/sky/shell/shell/classes.dex.jar',
@@ -218,9 +220,6 @@
       '$enginePath/build/android/ant/chromium-debug.keystore',
     ];
   } else {
-    androidSdkPath = AndroidDevice.getAndroidSdkPath();
-    if (androidSdkPath == null)
-      return null;
     List<ArtifactType> artifactTypes = <ArtifactType>[
       ArtifactType.androidIcuData,
       ArtifactType.androidClassesJar,
@@ -234,7 +233,6 @@
   }
 
   _ApkComponents components = new _ApkComponents();
-  components.androidSdk = new Directory(androidSdkPath);
   components.manifest = new File(manifest);
   components.icuData = new File(artifactPaths[0]);
   components.jars = [new File(artifactPaths[1])];
@@ -242,11 +240,7 @@
   components.debugKeystore = new File(artifactPaths[3]);
   components.resources = new Directory(resources);
 
-  await parseServiceConfigs(
-    components.services,
-    jars: components.jars,
-    androidSdk: components.androidSdk.path
-  );
+  await parseServiceConfigs(components.services, jars: components.jars);
 
   if (!components.resources.existsSync()) {
     // TODO(eseidel): This level should be higher when path is manually set.
@@ -254,16 +248,6 @@
     components.resources = null;
   }
 
-  if (!components.androidSdk.existsSync()) {
-    printError('Can not locate Android SDK: $androidSdkPath');
-    return null;
-  }
-  if (!(new _ApkBuilder(components.androidSdk.path).checkSdkPath())) {
-    printError('Can not locate expected Android SDK tools at $androidSdkPath');
-    printError('You must install version $_kAndroidPlatformVersion of the SDK platform');
-    printError('and version $_kBuildToolsVersion of the build tools.');
-    return null;
-  }
   for (File f in [
     components.manifest, components.icuData, components.libSkyShell, components.debugKeystore
   ]..addAll(components.jars)) {
@@ -281,7 +265,7 @@
 ) {
   Directory tempDir = Directory.systemTemp.createTempSync('flutter_tools');
   try {
-    _ApkBuilder builder = new _ApkBuilder(components.androidSdk.path);
+    _ApkBuilder builder = new _ApkBuilder(androidSdk.latestVersion);
 
     File classesDex = new File('${tempDir.path}/classes.dex');
     builder.compileClassesDex(classesDex, components.jars);
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index 0dee7cc..f95ea65 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -7,6 +7,7 @@
 import 'dart:io';
 
 import '../android/adb.dart';
+import '../android/android_sdk.dart';
 import '../android/device_android.dart';
 import '../base/context.dart';
 import '../base/globals.dart';
diff --git a/packages/flutter_tools/lib/src/commands/refresh.dart b/packages/flutter_tools/lib/src/commands/refresh.dart
index 35ff972..d4d5fc1 100644
--- a/packages/flutter_tools/lib/src/commands/refresh.dart
+++ b/packages/flutter_tools/lib/src/commands/refresh.dart
@@ -32,7 +32,7 @@
       downloadApplicationPackagesAndConnectToDevices(),
     ], eagerError: true);
 
-    if (!devices.android.isConnected()) {
+    if (devices.android == null || !devices.android.isConnected()) {
       printError('No device connected.');
       return 1;
     }
diff --git a/packages/flutter_tools/lib/src/commands/trace.dart b/packages/flutter_tools/lib/src/commands/trace.dart
index a40af6d..365d552 100644
--- a/packages/flutter_tools/lib/src/commands/trace.dart
+++ b/packages/flutter_tools/lib/src/commands/trace.dart
@@ -29,7 +29,7 @@
   Future<int> runInProject() async {
     await downloadApplicationPackagesAndConnectToDevices();
 
-    if (!devices.android.isConnected()) {
+    if (devices.android == null || !devices.android.isConnected()) {
       printError('No device connected, so no trace was completed.');
       return 1;
     }
diff --git a/packages/flutter_tools/lib/src/ios/device_ios.dart b/packages/flutter_tools/lib/src/ios/device_ios.dart
index 1e1eba8..4ab15bf 100644
--- a/packages/flutter_tools/lib/src/ios/device_ios.dart
+++ b/packages/flutter_tools/lib/src/ios/device_ios.dart
@@ -402,6 +402,8 @@
     if (!device.isConnected())
       return 2;
 
+    // TODO(devoncarew): This regex should use the CFBundleIdentifier value from
+    // the user's plist (instead of `flutter.runner.Runner`).
     return await runCommandAndStreamOutput(
       <String>[device.loggerPath],
       prefix: '[$name] ',
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index a01c21f..9f0cbc3 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -9,7 +9,9 @@
 import 'package:args/command_runner.dart';
 import 'package:path/path.dart' as path;
 
+import '../android/android_sdk.dart';
 import '../artifacts.dart';
+import '../base/context.dart';
 import '../base/globals.dart';
 import '../base/process.dart';
 import '../build_configuration.dart';
@@ -167,6 +169,22 @@
     // See if the user specified a specific device.
     deviceManager.specifiedDeviceId = globalResults['device-id'];
 
+    // The Android SDK could already have been set by tests.
+    if (!context.isSet(AndroidSdk)) {
+      if (enginePath != null) {
+        context[AndroidSdk] = new AndroidSdk('$enginePath/third_party/android_tools/sdk');
+      } else {
+        context[AndroidSdk] = AndroidSdk.locateAndroidSdk();
+      }
+    }
+
+    if (androidSdk != null) {
+      printTrace('Using Android SDK at ${androidSdk.directory}.');
+
+      if (androidSdk.latestVersion != null)
+        printTrace('${androidSdk.latestVersion}');
+    }
+
     ArtifactStore.flutterRoot = path.normalize(path.absolute(globalResults['flutter-root']));
     if (globalResults.wasParsed('package-root'))
       ArtifactStore.packageRoot = path.normalize(path.absolute(globalResults['package-root']));
diff --git a/packages/flutter_tools/lib/src/services.dart b/packages/flutter_tools/lib/src/services.dart
index 0225aec..f23d68a 100644
--- a/packages/flutter_tools/lib/src/services.dart
+++ b/packages/flutter_tools/lib/src/services.dart
@@ -10,6 +10,7 @@
 import 'package:yaml/yaml.dart';
 
 import 'artifacts.dart';
+import 'base/globals.dart';
 
 const String _kFlutterManifestPath = 'flutter.yaml';
 
@@ -23,7 +24,7 @@
 /// Loads all services specified in `flutter.yaml`. Parses each service config file,
 /// storing metadata in [services] and the list of jar files in [jars].
 Future parseServiceConfigs(
-  List<Map<String, String>> services, { List<File> jars, String androidSdk }
+  List<Map<String, String>> services, { List<File> jars }
 ) async {
   if (!ArtifactStore.isPackageRootValid)
     return;
@@ -49,17 +50,17 @@
 
     if (jars != null) {
       for (String jar in serviceConfig['jars'])
-        jars.add(new File(await getServiceFromUrl(jar, serviceRoot, service, androidSdk: androidSdk, unzip: false)));
+        jars.add(new File(await getServiceFromUrl(jar, serviceRoot, service, unzip: false)));
     }
   }
 }
 
 Future<String> getServiceFromUrl(
-  String url, String rootDir, String serviceName, { String androidSdk, bool unzip: false }
+  String url, String rootDir, String serviceName, { bool unzip: false }
 ) async {
-  if (url.startsWith("android-sdk:")) {
+  if (url.startsWith("android-sdk:") && androidSdk != null) {
     // It's something shipped in the standard android SDK.
-    return url.replaceAll('android-sdk:', '$androidSdk/');
+    return url.replaceAll('android-sdk:', '${androidSdk.directory}/');
   } else if (url.startsWith("http")) {
     // It's a regular file to download.
     return await ArtifactStore.getThirdPartyFile(url, serviceName, unzip);