application_package_test.dart 33.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8
import 'package:file/file.dart';
import 'package:file/memory.dart';
9
import 'package:flutter_tools/src/android/android_sdk.dart';
10
import 'package:flutter_tools/src/android/application_package.dart';
11 12
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/file_system.dart';
13
import 'package:flutter_tools/src/base/logger.dart';
14
import 'package:flutter_tools/src/base/os.dart';
15
import 'package:flutter_tools/src/base/process.dart';
16
import 'package:flutter_tools/src/base/user_messages.dart';
17 18
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
19
import 'package:flutter_tools/src/fuchsia/application_package.dart';
20
import 'package:flutter_tools/src/globals.dart' as globals;
21
import 'package:flutter_tools/src/ios/application_package.dart';
22
import 'package:flutter_tools/src/ios/plist_parser.dart';
23
import 'package:flutter_tools/src/project.dart';
24
import 'package:test/fake.dart';
25

26 27
import '../src/common.dart';
import '../src/context.dart';
28
import '../src/fakes.dart';
29 30

void main() {
31
  group('Apk with partial Android SDK works', () {
32
    FakeAndroidSdk sdk;
33
    FakeProcessManager fakeProcessManager;
34
    MemoryFileSystem fs;
35
    Cache cache;
36

37 38
    final Map<Type, Generator> overrides = <Type, Generator>{
      AndroidSdk: () => sdk,
39
      ProcessManager: () => fakeProcessManager,
40
      FileSystem: () => fs,
41
      Cache: () => cache,
42 43 44
    };

    setUp(() async {
45
      sdk = FakeAndroidSdk();
46
      fakeProcessManager = FakeProcessManager.empty();
47
      fs = MemoryFileSystem.test();
48
      cache = Cache.test(
49
        processManager: FakeProcessManager.any(),
50
      );
51
      Cache.flutterRoot = '../..';
52
      sdk.licensesAvailable = true;
53
      final FlutterProject project = FlutterProject.fromDirectoryTest(fs.currentDirectory);
54
      fs.file(project.android.hostAppGradleRoot.childFile(
55
        globals.platform.isWindows ? 'gradlew.bat' : 'gradlew',
56
      ).path).createSync(recursive: true);
57 58
    });

59 60
    testUsingContext('Licenses not available, platform and buildtools available, apk exists', () async {
      const String aaptPath = 'aaptPath';
61
      final File apkFile = globals.fs.file('app.apk');
62 63 64 65 66
      final FakeAndroidSdkVersion sdkVersion = FakeAndroidSdkVersion();
      sdkVersion.aaptPath = aaptPath;
      sdk.latestVersion = sdkVersion;
      sdk.platformToolsAvailable = true;
      sdk.licensesAvailable = false;
67 68 69 70

      fakeProcessManager.addCommand(
        FakeCommand(
          command: <String>[
71 72 73
            aaptPath,
            'dump',
            'xmltree',
74
             apkFile.path,
75
            'AndroidManifest.xml',
76 77 78 79
          ],
          stdout: _aaptDataWithDefaultEnabledAndMainLauncherActivity
        )
      );
80 81 82

      final ApplicationPackage applicationPackage = await ApplicationPackageFactory.instance.getPackageForPlatform(
        TargetPlatform.android_arm,
83
        buildInfo: null,
84 85 86
        applicationBinary: apkFile,
      );
      expect(applicationPackage.name, 'app.apk');
87 88
      expect(applicationPackage, isA<PrebuiltApplicationPackage>());
      expect((applicationPackage as PrebuiltApplicationPackage).applicationPackage.path, apkFile.path);
89
      expect(fakeProcessManager.hasRemainingExpectations, isFalse);
90 91
    }, overrides: overrides);

92
    testUsingContext('Licenses available, build tools not, apk exists', () async {
93
      sdk.latestVersion = null;
94
      final FlutterProject project = FlutterProject.fromDirectoryTest(fs.currentDirectory);
Emmanuel Garcia's avatar
Emmanuel Garcia committed
95 96 97 98
      project.android.hostAppGradleRoot
        .childFile('gradle.properties')
        .writeAsStringSync('irrelevant');

99
      final Directory gradleWrapperDir = cache.getArtifactDirectory('gradle_wrapper');
100

101
      gradleWrapperDir.fileSystem.directory(gradleWrapperDir.childDirectory('gradle').childDirectory('wrapper'))
102
          .createSync(recursive: true);
103 104
      gradleWrapperDir.childFile('gradlew').writeAsStringSync('irrelevant');
      gradleWrapperDir.childFile('gradlew.bat').writeAsStringSync('irrelevant');
105

106 107
      await ApplicationPackageFactory.instance.getPackageForPlatform(
        TargetPlatform.android_arm,
108
        buildInfo: null,
109
        applicationBinary: globals.fs.file('app.apk'),
110
      );
111
      expect(fakeProcessManager.hasRemainingExpectations, isFalse);
112 113 114
    }, overrides: overrides);

    testUsingContext('Licenses available, build tools available, does not call gradle dependencies', () async {
115 116
      final AndroidSdkVersion sdkVersion = FakeAndroidSdkVersion();
      sdk.latestVersion = sdkVersion;
117 118 119

      await ApplicationPackageFactory.instance.getPackageForPlatform(
        TargetPlatform.android_arm,
120
        buildInfo: null,
121
      );
122
      expect(fakeProcessManager.hasRemainingExpectations, isFalse);
123
    }, overrides: overrides);
124

125
    testWithoutContext('returns null when failed to extract manifest', () async {
126 127
      final AndroidSdkVersion sdkVersion = FakeAndroidSdkVersion();
      sdk.latestVersion = sdkVersion;
128
      final Logger logger = BufferLogger.test();
129 130 131
      final AndroidApk androidApk = AndroidApk.fromApk(
        null,
        processManager: fakeProcessManager,
132
        logger: logger,
133 134
        userMessages: UserMessages(),
        androidSdk: sdk,
135
        processUtils: ProcessUtils(processManager: fakeProcessManager, logger: logger),
136
      );
137

138
      expect(androidApk, isNull);
139
      expect(fakeProcessManager.hasRemainingExpectations, isFalse);
140
    });
141 142
  });

143
  group('ApkManifestData', () {
144 145 146 147 148 149
    testWithoutContext('Parses manifest with an Activity that has enabled set to true, action set to android.intent.action.MAIN and category set to android.intent.category.LAUNCHER', () {
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithExplicitEnabledAndMainLauncherActivity,
        BufferLogger.test(),
      );

150
      expect(data, isNotNull);
151 152
      expect(data.packageName, 'io.flutter.examples.hello_world');
      expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2');
153 154 155 156 157 158 159
    });

    testWithoutContext('Parses manifest with an Activity that has no value for its enabled field, action set to android.intent.action.MAIN and category set to android.intent.category.LAUNCHER', () {
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithDefaultEnabledAndMainLauncherActivity,
        BufferLogger.test(),
      );
160

161 162 163
      expect(data, isNotNull);
      expect(data.packageName, 'io.flutter.examples.hello_world');
      expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity2');
164 165 166 167 168 169 170
    });

    testWithoutContext('Parses manifest with a dist namespace', () {
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithDistNamespace,
        BufferLogger.test(),
      );
171 172 173 174

      expect(data, isNotNull);
      expect(data.packageName, 'io.flutter.examples.hello_world');
      expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity');
175 176 177 178 179 180 181 182
    });

    testWithoutContext('Error when parsing manifest with no Activity that has enabled set to true nor has no value for its enabled field', () {
      final BufferLogger logger = BufferLogger.test();
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithNoEnabledActivity,
        logger,
      );
183

184 185
      expect(data, isNull);
      expect(
186 187 188 189 190 191 192 193 194 195 196
        logger.errorText,
        'Error running io.flutter.examples.hello_world. Default activity not found\n',
      );
    });

    testWithoutContext('Error when parsing manifest with no Activity that has action set to android.intent.action.MAIN', () {
      final BufferLogger logger = BufferLogger.test();
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithNoMainActivity,
        logger,
      );
197

198 199
      expect(data, isNull);
      expect(
200 201 202 203 204 205 206 207 208 209 210
        logger.errorText,
        'Error running io.flutter.examples.hello_world. Default activity not found\n',
      );
    });

    testWithoutContext('Error when parsing manifest with no Activity that has category set to android.intent.category.LAUNCHER', () {
      final BufferLogger logger = BufferLogger.test();
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithNoLauncherActivity,
        logger,
      );
211

212 213
      expect(data, isNull);
      expect(
214 215 216 217 218 219 220 221 222 223
        logger.errorText,
        'Error running io.flutter.examples.hello_world. Default activity not found\n',
      );
    });

    testWithoutContext('Parsing manifest with Activity that has multiple category, android.intent.category.LAUNCHER and android.intent.category.DEFAULT', () {
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithLauncherAndDefaultActivity,
        BufferLogger.test(),
      );
224 225 226 227

      expect(data, isNotNull);
      expect(data.packageName, 'io.flutter.examples.hello_world');
      expect(data.launchableActivityName, 'io.flutter.examples.hello_world.MainActivity');
228
    });
229

230 231 232 233 234
    testWithoutContext('Parses manifest with missing application tag', () async {
      final ApkManifestData data = ApkManifestData.parseFromXmlDump(
        _aaptDataWithoutApplication,
        BufferLogger.test(),
      );
235 236 237

      expect(data, isNull);
    });
238
  });
239

240
  group('PrebuiltIOSApp', () {
241
    FakeOperatingSystemUtils os;
242 243
    FakePlistParser testPlistParser;

244
    final Map<Type, Generator> overrides = <Type, Generator>{
245
      FileSystem: () => MemoryFileSystem.test(),
246
      ProcessManager: () => FakeProcessManager.any(),
247
      PlistParser: () => testPlistParser,
248
      OperatingSystemUtils: () => os,
249
    };
250

251
    setUp(() {
252
      os = FakeOperatingSystemUtils();
253
      testPlistParser = FakePlistParser();
254 255
    });

256 257
    testUsingContext('Error on non-existing file', () {
      final PrebuiltIOSApp iosApp =
258
          IOSApp.fromPrebuiltApp(globals.fs.file('not_existing.ipa')) as PrebuiltIOSApp;
259 260
      expect(iosApp, isNull);
      expect(
261
        testLogger.errorText,
262 263 264
        'File "not_existing.ipa" does not exist. Use an app bundle or an ipa.\n',
      );
    }, overrides: overrides);
265

266
    testUsingContext('Error on non-app-bundle folder', () {
267
      globals.fs.directory('regular_folder').createSync();
268
      final PrebuiltIOSApp iosApp =
269
          IOSApp.fromPrebuiltApp(globals.fs.file('regular_folder')) as PrebuiltIOSApp;
270 271
      expect(iosApp, isNull);
      expect(
272
          testLogger.errorText, 'Folder "regular_folder" is not an app bundle.\n');
273
    }, overrides: overrides);
274

275
    testUsingContext('Error on no info.plist', () {
276 277
      globals.fs.directory('bundle.app').createSync();
      final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('bundle.app')) as PrebuiltIOSApp;
278 279
      expect(iosApp, isNull);
      expect(
280
        testLogger.errorText,
281 282 283
        'Invalid prebuilt iOS app. Does not contain Info.plist.\n',
      );
    }, overrides: overrides);
284

285
    testUsingContext('Error on bad info.plist', () {
286
      globals.fs.directory('bundle.app').createSync();
287
      globals.fs.file('bundle.app/Info.plist').createSync();
288
      final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('bundle.app')) as PrebuiltIOSApp;
289 290
      expect(iosApp, isNull);
      expect(
291
        testLogger.errorText,
292 293 294 295
        contains(
            'Invalid prebuilt iOS app. Info.plist does not contain bundle identifier\n'),
      );
    }, overrides: overrides);
296

297
    testUsingContext('Success with app bundle', () {
298
      globals.fs.directory('bundle.app').createSync();
299 300
      globals.fs.file('bundle.app/Info.plist').createSync();
      testPlistParser.setProperty('CFBundleIdentifier', 'fooBundleId');
301
      final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('bundle.app')) as PrebuiltIOSApp;
302
      expect(testLogger.errorText, isEmpty);
303
      expect(iosApp.uncompressedBundle.path, 'bundle.app');
304 305
      expect(iosApp.id, 'fooBundleId');
      expect(iosApp.bundleName, 'bundle.app');
306
      expect(iosApp.applicationPackage.path, globals.fs.directory('bundle.app').path);
307
    }, overrides: overrides);
308

309
    testUsingContext('Bad ipa zip-file, no payload dir', () {
310 311
      globals.fs.file('app.ipa').createSync();
      final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('app.ipa')) as PrebuiltIOSApp;
312 313
      expect(iosApp, isNull);
      expect(
314
        testLogger.errorText,
315 316 317
        'Invalid prebuilt iOS ipa. Does not contain a "Payload" directory.\n',
      );
    }, overrides: overrides);
318

319
    testUsingContext('Bad ipa zip-file, two app bundles', () {
320
      globals.fs.file('app.ipa').createSync();
321
      os.onUnzip = (File zipFile, Directory targetDirectory) {
322
        if (zipFile.path != 'app.ipa') {
323
          return;
324 325
        }
        final String bundlePath1 =
326
            globals.fs.path.join(targetDirectory.path, 'Payload', 'bundle1.app');
327
        final String bundlePath2 =
328 329 330
            globals.fs.path.join(targetDirectory.path, 'Payload', 'bundle2.app');
        globals.fs.directory(bundlePath1).createSync(recursive: true);
        globals.fs.directory(bundlePath2).createSync(recursive: true);
331
      };
332
      final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('app.ipa')) as PrebuiltIOSApp;
333
      expect(iosApp, isNull);
334
      expect(testLogger.errorText,
335 336
          'Invalid prebuilt iOS ipa. Does not contain a single app bundle.\n');
    }, overrides: overrides);
337

338
    testUsingContext('Success with ipa', () {
339
      globals.fs.file('app.ipa').createSync();
340
      os.onUnzip = (File zipFile, Directory targetDirectory) {
341
        if (zipFile.path != 'app.ipa') {
342
          return;
343
        }
344 345
        final Directory bundleAppDir = globals.fs.directory(
            globals.fs.path.join(targetDirectory.path, 'Payload', 'bundle.app'));
346
        bundleAppDir.createSync(recursive: true);
347
        testPlistParser.setProperty('CFBundleIdentifier', 'fooBundleId');
348 349
        globals.fs
            .file(globals.fs.path.join(bundleAppDir.path, 'Info.plist'))
350
            .createSync();
351
      };
352
      final PrebuiltIOSApp iosApp = IOSApp.fromPrebuiltApp(globals.fs.file('app.ipa')) as PrebuiltIOSApp;
353
      expect(testLogger.errorText, isEmpty);
354
      expect(iosApp.uncompressedBundle.path, endsWith('bundle.app'));
355 356
      expect(iosApp.id, 'fooBundleId');
      expect(iosApp.bundleName, 'bundle.app');
357
      expect(iosApp.applicationPackage.path, globals.fs.file('app.ipa').path);
358
    }, overrides: overrides);
359 360

    testUsingContext('returns null when there is no ios or .ios directory', () async {
361 362
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages').createSync();
363
      final BuildableIOSApp iosApp = await IOSApp.fromIosProject(
364
        FlutterProject.fromDirectory(globals.fs.currentDirectory).ios, null) as BuildableIOSApp;
365 366 367

      expect(iosApp, null);
    }, overrides: overrides);
368 369

    testUsingContext('returns null when there is no Runner.xcodeproj', () async {
370 371 372
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages').createSync();
      globals.fs.file('ios/FooBar.xcodeproj').createSync(recursive: true);
373
      final BuildableIOSApp iosApp = await IOSApp.fromIosProject(
374
        FlutterProject.fromDirectory(globals.fs.currentDirectory).ios, null) as BuildableIOSApp;
375 376 377

      expect(iosApp, null);
    }, overrides: overrides);
378 379

    testUsingContext('returns null when there is no Runner.xcodeproj/project.pbxproj', () async {
380 381 382
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages').createSync();
      globals.fs.file('ios/Runner.xcodeproj').createSync(recursive: true);
383
      final BuildableIOSApp iosApp = await IOSApp.fromIosProject(
384
        FlutterProject.fromDirectory(globals.fs.currentDirectory).ios, null) as BuildableIOSApp;
385 386 387

      expect(iosApp, null);
    }, overrides: overrides);
388 389 390 391 392 393 394 395 396 397 398

    testUsingContext('returns null when there with no product identifier', () async {
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages').createSync();
      final Directory project = globals.fs.directory('ios/Runner.xcodeproj')..createSync(recursive: true);
      project.childFile('project.pbxproj').createSync();
      final BuildableIOSApp iosApp = await IOSApp.fromIosProject(
          FlutterProject.fromDirectory(globals.fs.currentDirectory).ios, null) as BuildableIOSApp;

      expect(iosApp, null);
    }, overrides: overrides);
399
  });
400 401 402

  group('FuchsiaApp', () {
    final Map<Type, Generator> overrides = <Type, Generator>{
403
      FileSystem: () => MemoryFileSystem.test(),
404
      ProcessManager: () => FakeProcessManager.any(),
405
      OperatingSystemUtils: () => FakeOperatingSystemUtils(),
406
    };
407

408 409
    testUsingContext('Error on non-existing file', () {
      final PrebuiltFuchsiaApp fuchsiaApp =
410
          FuchsiaApp.fromPrebuiltApp(globals.fs.file('not_existing.far')) as PrebuiltFuchsiaApp;
411 412
      expect(fuchsiaApp, isNull);
      expect(
413
        testLogger.errorText,
414 415 416 417 418
        'File "not_existing.far" does not exist or is not a .far file. Use far archive.\n',
      );
    }, overrides: overrides);

    testUsingContext('Error on non-far file', () {
419
      globals.fs.directory('regular_folder').createSync();
420
      final PrebuiltFuchsiaApp fuchsiaApp =
421
          FuchsiaApp.fromPrebuiltApp(globals.fs.file('regular_folder')) as PrebuiltFuchsiaApp;
422 423
      expect(fuchsiaApp, isNull);
      expect(
424
        testLogger.errorText,
425 426 427 428 429
        'File "regular_folder" does not exist or is not a .far file. Use far archive.\n',
      );
    }, overrides: overrides);

    testUsingContext('Success with far file', () {
430 431
      globals.fs.file('bundle.far').createSync();
      final PrebuiltFuchsiaApp fuchsiaApp = FuchsiaApp.fromPrebuiltApp(globals.fs.file('bundle.far')) as PrebuiltFuchsiaApp;
432
      expect(testLogger.errorText, isEmpty);
433
      expect(fuchsiaApp.id, 'bundle.far');
434
      expect(fuchsiaApp.applicationPackage.path, globals.fs.file('bundle.far').path);
435 436 437
    }, overrides: overrides);

    testUsingContext('returns null when there is no fuchsia', () async {
438 439 440
      globals.fs.file('pubspec.yaml').createSync();
      globals.fs.file('.packages').createSync();
      final BuildableFuchsiaApp fuchsiaApp = FuchsiaApp.fromFuchsiaProject(FlutterProject.fromDirectory(globals.fs.currentDirectory).fuchsia) as BuildableFuchsiaApp;
441 442 443 444

      expect(fuchsiaApp, null);
    }, overrides: overrides);
  });
445 446
}

447 448
const String _aaptDataWithExplicitEnabledAndMainLauncherActivity = '''
N: android=http://schemas.android.com/apk/res/android
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
  E: manifest (line=7)
    A: android:versionCode(0x0101021b)=(type 0x10)0x1
    A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
    A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
    E: uses-sdk (line=12)
      A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
      A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
    E: uses-permission (line=21)
      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
    E: application (line=29)
      A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
      A: android:icon(0x01010002)=@0x7f010000
      A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
      A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
      E: activity (line=34)
        A: android:theme(0x01010000)=@0x1030009
        A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
        A: android:enabled(0x0101000e)=(type 0x12)0x0
        A: android:launchMode(0x0101001d)=(type 0x10)0x1
        A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
        A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
        A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
        E: intent-filter (line=42)
          E: action (line=43)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
          E: category (line=45)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
      E: activity (line=48)
        A: android:theme(0x01010000)=@0x1030009
        A: android:label(0x01010001)="app2" (Raw: "app2")
        A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity2" (Raw: "io.flutter.examples.hello_world.MainActivity2")
        A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
        E: intent-filter (line=53)
          E: action (line=54)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
          E: category (line=56)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';


488 489
const String _aaptDataWithDefaultEnabledAndMainLauncherActivity = '''
N: android=http://schemas.android.com/apk/res/android
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
  E: manifest (line=7)
    A: android:versionCode(0x0101021b)=(type 0x10)0x1
    A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
    A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
    E: uses-sdk (line=12)
      A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
      A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
    E: uses-permission (line=21)
      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
    E: application (line=29)
      A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
      A: android:icon(0x01010002)=@0x7f010000
      A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
      A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
      E: activity (line=34)
        A: android:theme(0x01010000)=@0x1030009
        A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
        A: android:enabled(0x0101000e)=(type 0x12)0x0
        A: android:launchMode(0x0101001d)=(type 0x10)0x1
        A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
        A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
        A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
        E: intent-filter (line=42)
          E: action (line=43)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
          E: category (line=45)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
      E: activity (line=48)
        A: android:theme(0x01010000)=@0x1030009
        A: android:label(0x01010001)="app2" (Raw: "app2")
        A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity2" (Raw: "io.flutter.examples.hello_world.MainActivity2")
        E: intent-filter (line=53)
          E: action (line=54)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
          E: category (line=56)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';


528 529
const String _aaptDataWithNoEnabledActivity = '''
N: android=http://schemas.android.com/apk/res/android
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
  E: manifest (line=7)
    A: android:versionCode(0x0101021b)=(type 0x10)0x1
    A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
    A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
    E: uses-sdk (line=12)
      A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
      A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
    E: uses-permission (line=21)
      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
    E: application (line=29)
      A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
      A: android:icon(0x01010002)=@0x7f010000
      A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
      A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
      E: activity (line=34)
        A: android:theme(0x01010000)=@0x1030009
        A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
        A: android:enabled(0x0101000e)=(type 0x12)0x0
        A: android:launchMode(0x0101001d)=(type 0x10)0x1
        A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
        A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
        A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
        E: intent-filter (line=42)
          E: action (line=43)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
          E: category (line=45)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';

558 559
const String _aaptDataWithNoMainActivity = '''
N: android=http://schemas.android.com/apk/res/android
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585
  E: manifest (line=7)
    A: android:versionCode(0x0101021b)=(type 0x10)0x1
    A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
    A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
    E: uses-sdk (line=12)
      A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
      A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
    E: uses-permission (line=21)
      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
    E: application (line=29)
      A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
      A: android:icon(0x01010002)=@0x7f010000
      A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
      A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
      E: activity (line=34)
        A: android:theme(0x01010000)=@0x1030009
        A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
        A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
        A: android:launchMode(0x0101001d)=(type 0x10)0x1
        A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
        A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
        A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
        E: intent-filter (line=42)
          E: category (line=43)
            A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")''';

586 587
const String _aaptDataWithNoLauncherActivity = '''
N: android=http://schemas.android.com/apk/res/android
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
  E: manifest (line=7)
    A: android:versionCode(0x0101021b)=(type 0x10)0x1
    A: android:versionName(0x0101021c)="0.0.1" (Raw: "0.0.1")
    A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
    E: uses-sdk (line=12)
      A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
      A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1b
    E: uses-permission (line=21)
      A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
    E: application (line=29)
      A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
      A: android:icon(0x01010002)=@0x7f010000
      A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
      A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
      E: activity (line=34)
        A: android:theme(0x01010000)=@0x1030009
        A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
        A: android:enabled(0x0101000e)=(type 0x12)0xffffffff
        A: android:launchMode(0x0101001d)=(type 0x10)0x1
        A: android:configChanges(0x0101001f)=(type 0x11)0x400035b4
        A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
        A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
        E: intent-filter (line=42)
          E: action (line=43)
            A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")''';

614 615
const String _aaptDataWithLauncherAndDefaultActivity = '''
N: android=http://schemas.android.com/apk/res/android
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
  N: dist=http://schemas.android.com/apk/distribution
    E: manifest (line=7)
      A: android:versionCode(0x0101021b)=(type 0x10)0x1
      A: android:versionName(0x0101021c)="1.0" (Raw: "1.0")
      A: android:compileSdkVersion(0x01010572)=(type 0x10)0x1c
      A: android:compileSdkVersionCodename(0x01010573)="9" (Raw: "9")
      A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
      A: platformBuildVersionCode=(type 0x10)0x1
      A: platformBuildVersionName=(type 0x4)0x3f800000
      E: uses-sdk (line=13)
        A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
        A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1c
      E: dist:module (line=17)
        A: dist:instant=(type 0x12)0xffffffff
      E: uses-permission (line=24)
        A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
      E: application (line=32)
        A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
        A: android:icon(0x01010002)=@0x7f010000
        A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
        E: activity (line=36)
          A: android:theme(0x01010000)=@0x01030009
          A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
          A: android:launchMode(0x0101001d)=(type 0x10)0x1
          A: android:configChanges(0x0101001f)=(type 0x11)0x400037b4
          A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
          A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
          E: intent-filter (line=43)
            E: action (line=44)
              A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
            E: category (line=46)
              A: android:name(0x01010003)="android.intent.category.DEFAULT" (Raw: "android.intent.category.DEFAULT")
            E: category (line=47)
              A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
''';

652 653
const String _aaptDataWithDistNamespace = '''
N: android=http://schemas.android.com/apk/res/android
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
  N: dist=http://schemas.android.com/apk/distribution
    E: manifest (line=7)
      A: android:versionCode(0x0101021b)=(type 0x10)0x1
      A: android:versionName(0x0101021c)="1.0" (Raw: "1.0")
      A: android:compileSdkVersion(0x01010572)=(type 0x10)0x1c
      A: android:compileSdkVersionCodename(0x01010573)="9" (Raw: "9")
      A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
      A: platformBuildVersionCode=(type 0x10)0x1
      A: platformBuildVersionName=(type 0x4)0x3f800000
      E: uses-sdk (line=13)
        A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
        A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1c
      E: dist:module (line=17)
        A: dist:instant=(type 0x12)0xffffffff
      E: uses-permission (line=24)
        A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
      E: application (line=32)
        A: android:label(0x01010001)="hello_world" (Raw: "hello_world")
        A: android:icon(0x01010002)=@0x7f010000
        A: android:name(0x01010003)="io.flutter.app.FlutterApplication" (Raw: "io.flutter.app.FlutterApplication")
        E: activity (line=36)
          A: android:theme(0x01010000)=@0x01030009
          A: android:name(0x01010003)="io.flutter.examples.hello_world.MainActivity" (Raw: "io.flutter.examples.hello_world.MainActivity")
          A: android:launchMode(0x0101001d)=(type 0x10)0x1
          A: android:configChanges(0x0101001f)=(type 0x11)0x400037b4
          A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
          A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
          E: intent-filter (line=43)
            E: action (line=44)
              A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
            E: category (line=46)
              A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
''';

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
const String _aaptDataWithoutApplication = '''
N: android=http://schemas.android.com/apk/res/android
  N: dist=http://schemas.android.com/apk/distribution
    E: manifest (line=7)
      A: android:versionCode(0x0101021b)=(type 0x10)0x1
      A: android:versionName(0x0101021c)="1.0" (Raw: "1.0")
      A: android:compileSdkVersion(0x01010572)=(type 0x10)0x1c
      A: android:compileSdkVersionCodename(0x01010573)="9" (Raw: "9")
      A: package="io.flutter.examples.hello_world" (Raw: "io.flutter.examples.hello_world")
      A: platformBuildVersionCode=(type 0x10)0x1
      A: platformBuildVersionName=(type 0x4)0x3f800000
      E: uses-sdk (line=13)
        A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
        A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1c
      E: dist:module (line=17)
        A: dist:instant=(type 0x12)0xffffffff
      E: uses-permission (line=24)
        A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
''';

708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
  void Function(File, Directory) onUnzip;

  @override
  void unzip(File file, Directory targetDirectory) {
    onUnzip?.call(file, targetDirectory);
  }
}

class FakeAndroidSdk extends Fake implements AndroidSdk {
  @override
  bool platformToolsAvailable;

  @override
  bool licensesAvailable;

  @override
  AndroidSdkVersion latestVersion;
}

class FakeAndroidSdkVersion extends Fake implements AndroidSdkVersion {
  @override
  String aaptPath;
}