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