Stop using a prebuilt APK

Instead, require an AndroidManifest.xml and always build an APK.

Fixes #2517
diff --git a/dev/manual_tests/android/AndroidManifest.xml b/dev/manual_tests/android/AndroidManifest.xml
new file mode 100644
index 0000000..98a4eaf
--- /dev/null
+++ b/dev/manual_tests/android/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.flutter.tests.ManualTests" android:versionCode="1" android:versionName="0.0.1">
+
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <application android:label="Flutter Manual Tests" android:name="org.domokit.sky.shell.SkyApplication">
+        <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTask" android:name="org.domokit.sky.shell.SkyActivity" android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+ </manifest>
diff --git a/examples/hello_world/android/AndroidManifest.xml b/examples/hello_world/android/AndroidManifest.xml
new file mode 100644
index 0000000..6d96b4e
--- /dev/null
+++ b/examples/hello_world/android/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.flutter.examples.HelloWorld" android:versionCode="1" android:versionName="0.0.1">
+
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <application android:label="Flutter Hello" android:name="org.domokit.sky.shell.SkyApplication">
+        <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTask" android:name="org.domokit.sky.shell.SkyActivity" android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+ </manifest>
diff --git a/examples/layers/android/AndroidManifest.xml b/examples/layers/android/AndroidManifest.xml
new file mode 100644
index 0000000..e81d686
--- /dev/null
+++ b/examples/layers/android/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.flutter.examples.Layers" android:versionCode="1" android:versionName="0.0.1">
+
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <application android:label="Flutter Layers" android:name="org.domokit.sky.shell.SkyApplication">
+        <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTask" android:name="org.domokit.sky.shell.SkyActivity" android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+ </manifest>
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index b5a5bac..1b233b6 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -8,7 +8,6 @@
 import 'package:path/path.dart' as path;
 import 'package:xml/xml.dart' as xml;
 
-import 'artifacts.dart';
 import 'build_configuration.dart';
 import 'ios/plist_utils.dart';
 
@@ -36,32 +35,23 @@
 }
 
 class AndroidApk extends ApplicationPackage {
-  static const String _defaultName = 'SkyShell.apk';
-  static const String _defaultId = 'org.domokit.sky.shell';
-  static const String _defaultLaunchActivity = '$_defaultId/$_defaultId.SkyActivity';
-  static const String _defaultManifestPath = 'android/AndroidManifest.xml';
-  static const String _defaultOutputPath = 'build/app.apk';
-
   /// The path to the activity that should be launched.
-  /// Defaults to 'org.domokit.sky.shell/org.domokit.sky.shell.SkyActivity'
   final String launchActivity;
 
   AndroidApk({
     String localPath,
-    String id: _defaultId,
-    this.launchActivity: _defaultLaunchActivity
+    String id,
+    this.launchActivity
   }) : super(localPath: localPath, id: id) {
     assert(launchActivity != null);
   }
 
   /// Creates a new AndroidApk based on the information in the Android manifest.
-  static AndroidApk getCustomApk({
-    String localPath: _defaultOutputPath,
-    String manifest: _defaultManifestPath
-  }) {
-    if (!FileSystemEntity.isFileSync(manifest))
+  factory AndroidApk.fromBuildConfiguration(BuildConfiguration config) {
+    String manifestPath = path.join('android', 'AndroidManifest.xml');
+    if (!FileSystemEntity.isFileSync(manifestPath))
       return null;
-    String manifestString = new File(manifest).readAsStringSync();
+    String manifestString = new File(manifestPath).readAsStringSync();
     xml.XmlDocument document = xml.parse(manifestString);
 
     Iterable<xml.XmlElement> manifests = document.findElements('manifest');
@@ -81,6 +71,7 @@
     if (id == null || launchActivity == null)
       return null;
 
+    String localPath = path.join('build', 'app.apk');
     return new AndroidApk(localPath: localPath, id: id, launchActivity: launchActivity);
   }
 }
@@ -95,12 +86,12 @@
     if (getCurrentHostPlatform() != HostPlatform.mac)
       return null;
 
-    String plistPath = path.join("ios", "Info.plist");
+    String plistPath = path.join('ios', 'Info.plist');
     String value = getValueFromFile(plistPath, kCFBundleIdentifierKey);
     if (value == null)
       return null;
 
-    String projectDir = path.join("ios", ".generated");
+    String projectDir = path.join('ios', '.generated');
     return new IOSApp(iosProjectDir: projectDir, iosProjectBundleId: value);
   }
 
@@ -134,20 +125,7 @@
       switch (config.targetPlatform) {
         case TargetPlatform.android_arm:
           assert(android == null);
-          android = AndroidApk.getCustomApk();
-          // Fall back to the prebuilt or engine-provided apk if we can't build
-          // a custom one.
-          // TODO(mpcomplete): we should remove both these fallbacks.
-          if (android != null) {
-            break;
-          } else if (config.type != BuildType.prebuilt) {
-            String localPath = path.join(config.buildDir, 'apks', AndroidApk._defaultName);
-            android = new AndroidApk(localPath: localPath);
-          } else {
-            Artifact artifact = ArtifactStore.getArtifact(
-              type: ArtifactType.shell, targetPlatform: TargetPlatform.android_arm);
-            android = new AndroidApk(localPath: await ArtifactStore.getPath(artifact));
-          }
+          android = new AndroidApk.fromBuildConfiguration(config);
           break;
 
         case TargetPlatform.ios_arm:
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 19680d4..db27a19 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -74,13 +74,7 @@
 class ArtifactStore {
   static const List<Artifact> knownArtifacts = const <Artifact>[
     const Artifact._(
-      name: 'Sky Shell',
-      fileName: 'SkyShell.apk',
-      type: ArtifactType.shell,
-      targetPlatform: TargetPlatform.android_arm
-    ),
-    const Artifact._(
-      name: 'Sky Shell',
+      name: 'Flutter Tester',
       fileName: 'sky_shell',
       type: ArtifactType.shell,
       targetPlatform: TargetPlatform.linux_x64
diff --git a/packages/flutter_tools/lib/src/commands/apk.dart b/packages/flutter_tools/lib/src/commands/apk.dart
index 43114e8..d6dc01b 100644
--- a/packages/flutter_tools/lib/src/commands/apk.dart
+++ b/packages/flutter_tools/lib/src/commands/apk.dart
@@ -242,7 +242,7 @@
 
   if (!components.resources.existsSync()) {
     // TODO(eseidel): This level should be higher when path is manually set.
-    printStatus('Can not locate Resources: ${components.resources}, ignoring.');
+    printStatus('Cannot locate Resources: ${components.resources}, ignoring.');
     components.resources = null;
   }
 
@@ -250,7 +250,7 @@
     components.manifest, components.icuData, components.libSkyShell, components.debugKeystore
   ]..addAll(components.jars)) {
     if (!f.existsSync()) {
-      printError('Can not locate file: ${f.path}');
+      printError('Cannot locate file: ${f.path}');
       return null;
     }
   }
@@ -453,8 +453,8 @@
   String target: ''
 }) async {
   if (!FileSystemEntity.isFileSync(_kDefaultAndroidManifestPath)) {
-    printStatus('Using pre-built SkyShell.apk.');
-    return 0;
+    printError('Cannot build APK. Missing $_kDefaultAndroidManifestPath.');
+    return 1;
   }
 
   int result = await buildAndroid(
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 26c7dfc..09f627e 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -129,6 +129,18 @@
   }
 }
 
+String _getMissingPackageHintForPlatform(TargetPlatform platform) {
+  switch (platform) {
+    case TargetPlatform.android_arm:
+      return 'Is your project missing an android/AndroidManifest.xml?';
+    case TargetPlatform.ios_arm:
+    case TargetPlatform.ios_x64:
+      return 'Is your project missing an ios/Info.plist?';
+    default:
+      return null;
+  }
+}
+
 Future<int> startApp(
   Device device,
   ApplicationPackageStore applicationPackages,
@@ -157,7 +169,11 @@
   ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
 
   if (package == null) {
-    printError('No application found for ${device.platform}.');
+    String message = 'No application found for ${device.platform}.';
+    String hint = _getMissingPackageHintForPlatform(device.platform);
+    if (hint != null)
+      message += '\n$hint';
+    printError(message);
     return 1;
   }
 
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 62d6c36..52db59e 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -16,7 +16,11 @@
 
 class MockApplicationPackageStore extends ApplicationPackageStore {
   MockApplicationPackageStore() : super(
-    android: new AndroidApk(localPath: '/mock/path/to/android/SkyShell.apk'),
+    android: new AndroidApk(
+      localPath: '/mock/path/to/android/SkyShell.apk',
+      id: 'io.flutter.android.mock',
+      launchActivity: 'io.flutter.android.mock.MockActivity'
+    ),
     iOS: new IOSApp(
       iosProjectDir: '/mock/path/to/iOS/SkyShell.app',
       iosProjectBundleId: 'io.flutter.ios.mock'