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  // TODO: Add support for setting as production.
17  setNodeEnv('development');
18
19  await ensureNativeProjectAsync(projectRoot, { platform: 'android', install });
20
21  const props = await resolveOptionsAsync(projectRoot, options);
22
23  debug('Package name: ' + props.packageName);
24  Log.log('› Building app...');
25
26  const androidProjectRoot = path.join(projectRoot, 'android');
27
28  await assembleAsync(androidProjectRoot, {
29    variant: props.variant,
30    port: props.port,
31    appName: props.appName,
32    buildCache: props.buildCache,
33  });
34
35  const manager = await startBundlerAsync(projectRoot, {
36    port: props.port,
37    // If a scheme is specified then use that instead of the package name.
38    scheme: (await getSchemesForAndroidAsync(projectRoot))?.[0],
39    headless: !props.shouldStartBundler,
40  });
41
42  await installAppAsync(androidProjectRoot, props);
43
44  await manager.getDefaultDevServer().openCustomRuntimeAsync(
45    'emulator',
46    {
47      applicationId: props.packageName,
48    },
49    { device: props.device.device }
50  );
51
52  if (props.shouldStartBundler) {
53    logProjectLogsLocation();
54  }
55}
56
57async function installAppAsync(androidProjectRoot: string, props: ResolvedOptions) {
58  // Find the APK file path
59  const apkFile = await resolveInstallApkNameAsync(props.device.device, props);
60
61  if (apkFile) {
62    // Attempt to install the APK from the file path
63    const binaryPath = path.join(props.apkVariantDirectory, apkFile);
64    debug('Installing:', binaryPath);
65    await props.device.installAppAsync(binaryPath);
66  } else {
67    // If we cannot resolve the APK file path then we can attempt to install using Gradle.
68    // This offers more advanced resolution that we may not have first class support for.
69    Log.log('› Failed to locate binary file, installing with Gradle...');
70    await installAsync(androidProjectRoot, {
71      variant: props.variant ?? 'debug',
72      appName: props.appName ?? 'app',
73      port: props.port,
74    });
75  }
76}
77