1import path from 'path';
2
3import { Log } from '../../log';
4import { assembleAsync, installAsync } from '../../start/platforms/android/gradle';
5import { getSchemesForAndroidAsync } from '../../utils/scheme';
6import { ensureNativeProjectAsync } from '../ensureNativeProject';
7import { logProjectLogsLocation } from '../hints';
8import { startBundlerAsync } from '../startBundler';
9import { resolveInstallApkNameAsync } from './resolveInstallApkName';
10import { Options, ResolvedOptions, resolveOptionsAsync } from './resolveOptions';
11
12const debug = require('debug')('expo:run:android');
13
14export async function runAndroidAsync(projectRoot: string, { install, ...options }: Options) {
15  await ensureNativeProjectAsync(projectRoot, { platform: 'android', install });
16
17  const props = await resolveOptionsAsync(projectRoot, options);
18
19  debug('Package name: ' + props.packageName);
20  Log.log('› Building app...');
21
22  const androidProjectRoot = path.join(projectRoot, 'android');
23
24  await assembleAsync(androidProjectRoot, {
25    variant: props.variant,
26    port: props.port,
27    appName: props.appName,
28    buildCache: props.buildCache,
29  });
30
31  const manager = await startBundlerAsync(projectRoot, {
32    port: props.port,
33    // If a scheme is specified then use that instead of the package name.
34    scheme: (await getSchemesForAndroidAsync(projectRoot))?.[0],
35    headless: !props.shouldStartBundler,
36  });
37
38  await installAppAsync(androidProjectRoot, props);
39
40  await manager.getDefaultDevServer().openCustomRuntimeAsync(
41    'emulator',
42    {
43      applicationId: props.packageName,
44    },
45    { device: props.device.device }
46  );
47
48  if (props.shouldStartBundler) {
49    logProjectLogsLocation();
50  }
51}
52
53async function installAppAsync(androidProjectRoot: string, props: ResolvedOptions) {
54  // Find the APK file path
55  const apkFile = await resolveInstallApkNameAsync(props.device.device, props);
56
57  if (apkFile) {
58    // Attempt to install the APK from the file path
59    const binaryPath = path.join(props.apkVariantDirectory, apkFile);
60    debug('Installing:', binaryPath);
61    await props.device.installAppAsync(binaryPath);
62  } else {
63    // If we cannot resolve the APK file path then we can attempt to install using Gradle.
64    // This offers more advanced resolution that we may not have first class support for.
65    Log.log('› Failed to locate binary file, installing with Gradle...');
66    await installAsync(androidProjectRoot, {
67      variant: props.variant ?? 'debug',
68      appName: props.appName ?? 'app',
69      port: props.port,
70    });
71  }
72}
73